allow the canvas size to be set on the ImageEditor (#8127)

* add canvas size kwarg to imageeditor

* add changeset

* fix tests

* fix cropsize

* changes

* notebooks

* update docstrings

* fix type

* fix undefined dimensions

* Update image_editor.py

Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* fix type

* format

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
pngwn 2024-05-03 10:55:19 +01:00 committed by GitHub
parent f5b710c919
commit 24b2286a22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 59 additions and 20 deletions

View File

@ -0,0 +1,6 @@
---
"@gradio/imageeditor": minor
"gradio": minor
---
feat:allow the canvas size to be set on the `ImageEditor`

View File

@ -133,7 +133,7 @@ class ImageEditor(Component):
image_mode: Literal[
"1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"
] = "RGBA",
sources: Iterable[Literal["upload", "webcam", "clipboard"]] = (
sources: Iterable[Literal["upload", "webcam", "clipboard"]] | None = (
"upload",
"webcam",
"clipboard",
@ -160,12 +160,13 @@ class ImageEditor(Component):
brush: Brush | None | Literal[False] = None,
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
):
"""
Parameters:
value: Optional initial image(s) to populate the image editor. Should be a dictionary with keys: `background`, `layers`, and `composite`. The values corresponding to `background` and `composite` should be images or None, while `layers` should be a list of images. Images can be of type PIL.Image, np.array, or str filepath/URL. Or, the value can be a callable, in which case the function will be called whenever the app loads to set the initial value of the component.
height: The height of the displayed images, specified in pixels if a number is passed, or in CSS units if a string is passed.
width: The width of the displayed images, specified in pixels if a number is passed, or in CSS units if a string is passed.
height: The height of the component container, specified in pixels if a number is passed, or in CSS units if a string is passed.
width: The width of the component container, specified in pixels if a number is passed, or in CSS units if a string is passed.
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 that can be used to set the background 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.
type: The format the images are converted to before being passed into the prediction function. "numpy" converts the images to numpy arrays with shape (height, width, 3) and values from 0 to 255, "pil" converts the images to PIL image objects, "filepath" passes images as str filepaths to temporary copies of the images.
@ -189,7 +190,7 @@ class ImageEditor(Component):
brush: The options for the brush tool in the image editor. Should be an instance of the `gr.Brush` class, or None to use the default settings. Can also be False to hide the brush tool, which will also hide the eraser tool.
format: Format to save image if it does not already have a valid format (e.g. if the image is being returned to the frontend as a numpy array or PIL Image). The format should be supported by the PIL library. This parameter has no effect on SVG files.
layers: If True, will allow users to add layers to the image. If False, the layers option will be hidden.
canvas_size: The size of the default canvas in pixels. If a tuple, the first value is the width and the second value is the height. If None, the canvas size will be the same as the background image or 800 x 600 if no background image is provided.
"""
self._selectable = _selectable
self.mirror_webcam = mirror_webcam
@ -205,12 +206,15 @@ class ImageEditor(Component):
valid_sources = ["upload", "webcam", "clipboard"]
if isinstance(sources, str):
sources = [sources] # type: ignore
for source in sources:
if source not in valid_sources:
raise ValueError(
f"`sources` must be a list consisting of elements in {valid_sources}"
)
self.sources = sources
if sources is not None:
for source in sources:
if source not in valid_sources:
raise ValueError(
f"`sources` must be a list consisting of elements in {valid_sources}"
)
self.sources = sources
else:
self.sources = []
self.show_download_button = show_download_button
@ -227,6 +231,7 @@ class ImageEditor(Component):
self.blob_storage: dict[str, EditorDataBlobs] = {}
self.format = format
self.layers = layers
self.canvas_size = canvas_size
super().__init__(
label=label,

View File

@ -109,6 +109,7 @@ class Sketchpad(components.ImageEditor):
brush: Brush | None = None,
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
):
if not brush:
brush = Brush(colors=["#000000"], color_mode="fixed")
@ -140,6 +141,7 @@ class Sketchpad(components.ImageEditor):
brush=brush,
format=format,
layers=layers,
canvas_size=canvas_size,
)
@ -182,6 +184,7 @@ class Paint(components.ImageEditor):
brush: Brush | None = None,
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
):
super().__init__(
value=value,
@ -211,6 +214,7 @@ class Paint(components.ImageEditor):
brush=brush,
format=format,
layers=layers,
canvas_size=canvas_size,
)
@ -257,6 +261,7 @@ class ImageMask(components.ImageEditor):
brush: Brush | None = None,
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
):
if not brush:
brush = Brush(colors=["#000000"], color_mode="fixed")
@ -288,6 +293,7 @@ class ImageMask(components.ImageEditor):
brush=brush,
format=format,
layers=layers,
canvas_size=canvas_size,
)

View File

@ -54,6 +54,7 @@
export let server: {
accept_blobs: (a: any) => void;
};
export let canvas_size: [number, number] | undefined;
export let gradio: Gradio<{
change: never;
@ -175,6 +176,7 @@
/>
<InteractiveImageEditor
{canvas_size}
on:change={() => handle_history_change()}
bind:image_id
{crop_size}

View File

@ -62,7 +62,7 @@
import { create_pixi_app, type ImageBlobs } from "./utils/pixi";
import Controls from "./Controls.svelte";
export let antialias = true;
export let crop_size: [number, number] = [800, 600];
export let crop_size: [number, number] | undefined;
export let changeable = false;
export let history: boolean;
export let bg = false;
@ -73,8 +73,13 @@
change: void;
}>();
export let crop_constraint = false;
export let canvas_size: [number, number] | undefined;
let dimensions = writable(crop_size);
$: orig_canvas_size = canvas_size;
const BASE_DIMENSIONS: [number, number] = canvas_size || [800, 600];
let dimensions = writable(BASE_DIMENSIONS);
export let height = 0;
let editor_box: EditorContext["editor_box"] = writable({
@ -260,7 +265,10 @@
$: $position_spring && get_dimensions(canvas_wrap, pixi_target);
export function handle_remove(): void {
editor_context.reset(true, $dimensions);
editor_context.reset(
true,
orig_canvas_size ? orig_canvas_size : $dimensions
);
if (!sources.length) {
set_tool("draw");
} else {
@ -270,7 +278,12 @@
}
onMount(() => {
const app = create_pixi_app(pixi_target, ...crop_size, antialias);
const _size = (canvas_size ? canvas_size : crop_size) || [800, 600];
const app = create_pixi_app({
target: pixi_target,
dimensions: _size,
antialias
});
function resize(width: number, height: number): void {
app.resize(width, height);

View File

@ -44,6 +44,7 @@
export let layers: boolean;
export let accept_blobs: (a: any) => void;
export let status: "pending" | "complete" | "error" = "complete";
export let canvas_size: [number, number] | undefined;
export let realtime: boolean;
export let upload: Client["upload"];
export let stream_handler: Client["eventSource_factory"];
@ -203,6 +204,8 @@
label={label || i18n("image.image")}
/>
<ImageEditor
{canvas_size}
crop_size={Array.isArray(crop_size) ? crop_size : undefined}
bind:this={editor}
bind:height={editor_height}
{changeable}

View File

@ -73,12 +73,15 @@ export interface PixiApp {
* @param antialias Whether to use antialiasing
* @returns object with pixi container and renderer
*/
export function create_pixi_app(
target: HTMLElement,
width: number,
height: number,
antialias: boolean
): PixiApp {
export function create_pixi_app({
target,
dimensions: [width, height],
antialias
}: {
target: HTMLElement;
dimensions: [number, number];
antialias: boolean;
}): PixiApp {
const ratio = window.devicePixelRatio || 1;
const app = new Application({
width,

View File

@ -641,6 +641,7 @@ class TestImageEditor:
"server_fns": ["accept_blobs"],
"format": "webp",
"layers": True,
"canvas_size": None,
}
def test_process_example(self):