Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process and convert .svg files in Image #6500

Merged
merged 12 commits into from Nov 21, 2023
5 changes: 5 additions & 0 deletions .changeset/pretty-ads-do.md
@@ -0,0 +1,5 @@
---
"gradio": patch
---

fix:Process and convert .svg files in `Image`
16 changes: 12 additions & 4 deletions gradio/components/image.py
Expand Up @@ -24,8 +24,8 @@
class Image(StreamingInput, Component):
"""
Creates an image component that can be used to upload images (as an input) or display images (as an output).
Preprocessing: passes the uploaded image as a {numpy.array}, {PIL.Image} or {str} filepath depending on `type`.
Postprocessing: expects a {numpy.array}, {PIL.Image} or {str} or {pathlib.Path} filepath to an image and displays the image.
Preprocessing: passes the uploaded image as a {numpy.array}, {PIL.Image} or {str} filepath depending on `type`. For SVGs, the `type` parameter is ignored and the filepath of the SVG is returned.
Postprocessing: expects a {numpy.array}, {PIL.Image} or {str} or {pathlib.Path} filepath to an image and displays the image. For SVGs, the original file is returned.
hannahblair marked this conversation as resolved.
Show resolved Hide resolved
Examples-format: a {str} local filepath or URL to an image.
Demos: image_mod, image_mod_default_image
Guides: image-classification-in-pytorch, image-classification-in-tensorflow, image-classification-with-vision-transformers, create-your-own-friends-with-a-gan
Expand Down Expand Up @@ -75,7 +75,7 @@ def __init__(
width: Width of the displayed image in pixels.
image_mode: "RGB" if color, or "L" if black and white. See https://pillow.readthedocs.io/en/stable/handbook/concepts.html for other supported image modes and their meaning.
sources: List of sources for the image. "upload" creates a box where user can drop an image file, "webcam" allows user to take snapshot from their webcam, "clipboard" allows users to paste an image from the clipboard. If None, defaults to ["upload", "webcam", "clipboard"] if streaming is False, otherwise defaults to ["webcam"].
type: The format the image is converted to before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image.
type: The format the image is converted before being passed into the prediction function. "numpy" converts the image to a numpy array with shape (height, width, 3) and values from 0 to 255, "pil" converts the image to a PIL image object, "filepath" passes a str path to a temporary file containing the image. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.
label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
show_label: if True, will display label.
Expand Down Expand Up @@ -147,6 +147,7 @@ def preprocess(
) -> np.ndarray | _Image.Image | str | None:
if payload is None:
return payload
file_path = Path(payload.path)
if payload.orig_name:
p = Path(payload.orig_name)
name = p.stem
Expand All @@ -156,7 +157,11 @@ def preprocess(
else:
name = "image"
suffix = "png"
im = _Image.open(payload.path)

if suffix.lower() == "svg":
hannahblair marked this conversation as resolved.
Show resolved Hide resolved
return str(file_path)

im = _Image.open(file_path)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
im = im.convert(self.image_mode)
Expand All @@ -173,6 +178,9 @@ def postprocess(
) -> FileData | None:
if value is None:
return None

if isinstance(value, str) and value.lower().endswith(".svg"):
return FileData(path=value, orig_name=Path(value).name)
saved = image_utils.save_image(value, self.GRADIO_CACHE)
orig_name = Path(saved).name if Path(saved).exists() else None
return FileData(path=saved, orig_name=orig_name)
Expand Down
19 changes: 19 additions & 0 deletions js/app/test/files/gradio-logo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions js/app/test/image_component_events.spec.ts
Expand Up @@ -26,6 +26,22 @@ test("Image click-to-upload uploads image successfuly. Clear button dispatches e
await page.getByLabel("Remove Image").click();
await expect(page.getByLabel("# Clear Events")).toHaveValue("1");
await expect(page.getByLabel("# Change Events").first()).toHaveValue("2");

await Promise.all([
uploader.setInputFiles(["./test/files/gradio-logo.svg"]),
page.waitForResponse("**/upload?*?*")
]);

await expect(page.getByLabel("# Change Events").first()).toHaveValue("3");
await expect(await page.getByLabel("# Upload Events")).toHaveValue("2");
await expect(await page.getByLabel("# Change Events Output")).toHaveValue(
"2"
);

const SVGdownloadPromise = page.waitForEvent("download");
await page.getByLabel("Download").click();
const SVGdownload = await SVGdownloadPromise;
expect(SVGdownload.suggestedFilename()).toBe("gradio-logo.svg");
});

test("Image drag-to-upload uploads image successfuly.", async ({ page }) => {
Expand Down