Add placeholder param to Image and ImageEditor to replace upload image text (#8930)

This commit is contained in:
Hannah 2024-08-21 01:22:17 +02:00 committed by GitHub
parent 747013bbac
commit 41d5ab987b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 147 additions and 26 deletions

View File

@ -0,0 +1,9 @@
---
"@gradio/atoms": minor
"@gradio/image": minor
"@gradio/imageeditor": minor
"@gradio/simpleimage": minor
"gradio": minor
---
feat:Add `placeholder` param to Image and ImageEditor to replace upload image text

View File

@ -74,6 +74,7 @@ class Image(StreamingInput, Component):
key: int | str | None = None,
mirror_webcam: bool = True,
show_share_button: bool | None = None,
placeholder: str | None = None,
show_fullscreen_button: bool = True,
):
"""
@ -102,6 +103,7 @@ class Image(StreamingInput, Component):
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
mirror_webcam: If True webcam will be mirrored. Default is True.
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.
placeholder: Custom text for the upload area. Overrides default upload messages when provided. Accepts new lines and `#` to designate a heading.
show_fullscreen_button: If True, will show a fullscreen icon in the corner of the component that allows user to view the image in fullscreen mode. If False, icon does not appear.
"""
self.format = format
@ -141,6 +143,7 @@ class Image(StreamingInput, Component):
else show_share_button
)
self.show_fullscreen_button = show_fullscreen_button
self.placeholder = placeholder
super().__init__(
label=label,
every=every,

View File

@ -171,6 +171,7 @@ class ImageEditor(Component):
elem_classes: list[str] | str | None = None,
render: bool = True,
key: int | str | None = None,
placeholder: str | None = None,
mirror_webcam: bool = True,
show_share_button: bool | None = None,
_selectable: bool = False,
@ -205,6 +206,7 @@ class ImageEditor(Component):
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.
render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
placeholder: Custom text for the upload area. Overrides default upload messages when provided. Accepts new lines and `#` to designate a heading.
mirror_webcam: If True webcam will be mirrored. Default is True.
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.
crop_size: The size of the crop box in pixels. If a tuple, the first value is the width and the second value is the height. If a string, the value must be a ratio in the form `width:height` (e.g. "16:9").
@ -257,6 +259,7 @@ class ImageEditor(Component):
self.layers = layers
self.canvas_size = canvas_size
self.show_fullscreen_button = show_fullscreen_button
self.placeholder = placeholder
super().__init__(
label=label,

View File

@ -115,6 +115,7 @@ class Sketchpad(components.ImageEditor):
elem_classes: list[str] | str | None = None,
render: bool = True,
key: int | str | None = None,
placeholder: str | None = None,
mirror_webcam: bool = True,
show_share_button: bool | None = None,
_selectable: bool = False,
@ -150,6 +151,7 @@ class Sketchpad(components.ImageEditor):
elem_classes=elem_classes,
render=render,
key=key,
placeholder=placeholder,
mirror_webcam=mirror_webcam,
show_share_button=show_share_button,
_selectable=_selectable,
@ -210,6 +212,7 @@ class Paint(components.ImageEditor):
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
show_fullscreen_button: bool = True,
placeholder: str | None = None,
):
super().__init__(
value=value,
@ -243,6 +246,7 @@ class Paint(components.ImageEditor):
layers=layers,
canvas_size=canvas_size,
show_fullscreen_button=show_fullscreen_button,
placeholder=placeholder,
)
@ -285,6 +289,7 @@ class ImageMask(components.ImageEditor):
elem_classes: list[str] | str | None = None,
render: bool = True,
key: int | str | None = None,
placeholder: str | None = None,
mirror_webcam: bool = True,
show_share_button: bool | None = None,
_selectable: bool = False,
@ -320,6 +325,7 @@ class ImageMask(components.ImageEditor):
elem_classes=elem_classes,
render=render,
key=key,
placeholder=placeholder,
mirror_webcam=mirror_webcam,
show_share_button=show_share_button,
_selectable=_selectable,

View File

@ -1,6 +1,8 @@
<script lang="ts">
import type { I18nFormatter } from "@gradio/utils";
import { Upload as UploadIcon, ImagePaste } from "@gradio/icons";
import { inject } from "./utils/parse_placeholder";
export let type:
| "video"
| "image"
@ -13,6 +15,7 @@
export let message: string | undefined = undefined;
export let mode: "full" | "short" = "full";
export let hovered = false;
export let placeholder: string | undefined = undefined;
const defs = {
image: "upload_text.drop_image",
@ -23,6 +26,8 @@
gallery: "upload_text.drop_gallery",
clipboard: "upload_text.paste_clipboard"
};
$: [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
</script>
<div class="wrap">
@ -34,15 +39,33 @@
{/if}
</span>
{i18n(defs[type] || defs.file)}
{#if heading || paragraph}
{#if heading}
<h2>{heading}</h2>
{/if}
{#if paragraph}
<p>{paragraph}</p>
{/if}
{:else}
{i18n(defs[type] || defs.file)}
{#if mode !== "short"}
<span class="or">- {i18n("common.or")} -</span>
{message || i18n("upload_text.click_to_upload")}
{#if mode !== "short"}
<span class="or">- {i18n("common.or")} -</span>
{message || i18n("upload_text.click_to_upload")}
{/if}
{/if}
</div>
<style>
h2 {
font-size: var(--text-xl) !important;
}
p,
h2 {
white-space: pre-line;
}
.wrap {
display: flex;
flex-direction: column;
@ -53,6 +76,8 @@
line-height: var(--line-md);
height: 100%;
padding-top: var(--size-3);
text-align: center;
margin: auto var(--spacing-lg);
}
.or {

View File

@ -0,0 +1,27 @@
const RE_HEADING = /^(#\s*)(.+)$/m;
export function inject(text: string): [string | false, string | false] {
const trimmed_text = text.trim();
const heading_match = trimmed_text.match(RE_HEADING);
if (!heading_match) {
return [false, trimmed_text || false];
}
const [full_match, , heading_content] = heading_match;
const _heading = heading_content.trim();
if (trimmed_text === full_match) {
return [_heading, false];
}
const heading_end_index =
heading_match.index !== undefined
? heading_match.index + full_match.length
: 0;
const remaining_text = trimmed_text.substring(heading_end_index).trim();
const _paragraph = remaining_text || false;
return [_heading, _paragraph];
}

View File

@ -29,7 +29,8 @@
["swim", "swim"],
["jump", "jump"]
],
label: "Single-select Dropdown"
label: "Single-select Dropdown",
info: "This is a single-select dropdown"
}}
/>
<Story

View File

@ -16,6 +16,8 @@
}
}
};
let md = `# a heading! /n a new line! `;
</script>
<Template let:args>
@ -28,20 +30,7 @@
</Template>
<Story
name="static with label and download button"
args={{
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
url: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
orig_name: "cheetah.jpg"
},
show_label: true,
show_download_button: true
}}
/>
<Story
name="static with label and download button"
name="static with label, info and download button"
args={{
value: {
path: "https://gradio-builds.s3.amazonaws.com/demo-files/ghepardo-primo-piano.jpg",
@ -49,6 +38,7 @@
orig_name: "cheetah.jpg"
},
show_label: true,
placeholder: "This is a cheetah",
show_download_button: true
}}
play={async ({ canvasElement }) => {
@ -85,7 +75,8 @@
},
show_label: false,
show_download_button: false,
interactive: true
interactive: true,
placeholder: md
}}
play={async ({ canvasElement }) => {
const canvas = within(canvasElement);

View File

@ -51,6 +51,7 @@
export let streaming: boolean;
export let pending: boolean;
export let mirror_webcam: boolean;
export let placeholder: string | undefined = undefined;
export let show_fullscreen_button: boolean;
export let gradio: Gradio<{
@ -201,7 +202,7 @@
stream_handler={gradio.client.stream}
>
{#if active_source === "upload" || !active_source}
<UploadText i18n={gradio.i18n} type="image" />
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
{:else if active_source === "clipboard"}
<UploadText i18n={gradio.i18n} type="clipboard" mode="short" />
{:else}

View File

@ -214,6 +214,7 @@
orig_name: "cheetah.jpg"
},
type: "pil",
placeholder: "Upload an image of a cat",
sources: ["upload", "webcam"],
interactive: "true",
brush: {

View File

@ -44,6 +44,7 @@
"webcam"
];
export let interactive: boolean;
export let placeholder: string | undefined;
export let brush: Brush;
export let eraser: Eraser;
@ -214,6 +215,7 @@
status={loading_status?.status}
upload={gradio.client.upload}
stream_handler={gradio.client.stream}
{placeholder}
></InteractiveImageEditor>
</Block>
{/if}

View File

@ -25,6 +25,7 @@
import { Tools, Crop, Brush, Sources } from "./tools";
import { BlockLabel } from "@gradio/atoms";
import { Image as ImageIcon } from "@gradio/icons";
import { inject } from "./utils/parse_placeholder";
export let brush: IBrush | null;
export let eraser: Eraser | null;
@ -49,6 +50,7 @@
export let upload: Client["upload"];
export let stream_handler: Client["stream"];
export let dragging: boolean;
export let placeholder: string | undefined = undefined;
const dispatch = createEventDispatcher<{
clear?: never;
@ -197,6 +199,8 @@
let active_mode: "webcam" | "color" | null = null;
let editor_height = 0;
$: [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
</script>
<BlockLabel
@ -254,13 +258,22 @@
{#if !bg && !history && active_mode !== "webcam" && status !== "error"}
<div class="empty wrap" style:height={`${editor_height}px`}>
{#if sources && sources.length}
<div>Upload an image</div>
{#if heading || paragraph}
{#if heading}
<h2>{heading}</h2>
{/if}
{#if paragraph}
<p>{paragraph}</p>
{/if}
{:else}
<div>Upload an image</div>
{/if}
{/if}
{#if sources && sources.length && brush}
{#if sources && sources.length && brush && !placeholder}
<div class="or">or</div>
{/if}
{#if brush}
{#if brush && !placeholder}
<div>select the draw tool to start</div>
{/if}
</div>
@ -268,6 +281,15 @@
</ImageEditor>
<style>
h2 {
font-size: var(--text-xl);
}
p,
h2 {
white-space: pre-line;
}
.empty {
display: flex;
flex-direction: column;
@ -292,7 +314,7 @@
align-items: center;
color: var(--block-label-text-color);
line-height: var(--line-md);
font-size: var(--text-lg);
font-size: var(--text-md);
pointer-events: none;
}

View File

@ -0,0 +1,27 @@
const RE_HEADING = /^(#\s*)(.+)$/m;
export function inject(text: string): [string | false, string | false] {
const trimmed_text = text.trim();
const heading_match = trimmed_text.match(RE_HEADING);
if (!heading_match) {
return [false, trimmed_text || false];
}
const [full_match, , heading_content] = heading_match;
const _heading = heading_content.trim();
if (trimmed_text === full_match) {
return [_heading, false];
}
const heading_end_index =
heading_match.index !== undefined
? heading_match.index + full_match.length
: 0;
const remaining_text = trimmed_text.substring(heading_end_index).trim();
const _paragraph = remaining_text || false;
return [_heading, _paragraph];
}

View File

@ -29,6 +29,7 @@
export let loading_status: LoadingStatus;
export let interactive: boolean;
export let root: string;
export let placeholder: string | undefined = undefined;
export let gradio: Gradio<{
change: never;
@ -100,7 +101,7 @@
{label}
{show_label}
>
<UploadText i18n={gradio.i18n} type="image" />
<UploadText i18n={gradio.i18n} type="image" {placeholder} />
</ImageUploader>
</Block>
{/if}

View File

@ -54,6 +54,7 @@ class TestImage:
"key": None,
"streamable": False,
"type": "pil",
"placeholder": None,
}
assert image_input.preprocess(None) is None
image_input = gr.Image()

View File

@ -69,6 +69,7 @@ class TestImageEditor:
"format": "webp",
"layers": True,
"canvas_size": None,
"placeholder": None,
"show_fullscreen_button": True,
}