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

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

Merged
merged 16 commits into from
May 3, 2024
6 changes: 6 additions & 0 deletions .changeset/real-chefs-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@gradio/imageeditor": minor
"gradio": minor
---

feat:allow the canvas size to be set on the `ImageEditor`
25 changes: 15 additions & 10 deletions gradio/components/image_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __init__(
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",
Expand All @@ -160,12 +160,13 @@ def __init__(
brush: Brush | None | Literal[False] = None,
format: str = "webp",
layers: bool = True,
canvas_size: Tuple[int, int] | None = None,
pngwn marked this conversation as resolved.
Show resolved Hide resolved
):
"""
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.
Expand All @@ -189,7 +190,7 @@ def __init__(
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
Expand All @@ -205,12 +206,15 @@ def __init__(
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

Expand All @@ -227,6 +231,7 @@ def __init__(
self.blob_storage: dict[str, EditorDataBlobs] = {}
self.format = format
self.layers = layers
self.canvas_size = canvas_size

super().__init__(
label=label,
Expand Down
6 changes: 6 additions & 0 deletions gradio/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(
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")
Expand Down Expand Up @@ -140,6 +141,7 @@ def __init__(
brush=brush,
format=format,
layers=layers,
canvas_size=canvas_size,
)


Expand Down Expand Up @@ -182,6 +184,7 @@ def __init__(
brush: Brush | None = None,
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
):
super().__init__(
value=value,
Expand Down Expand Up @@ -211,6 +214,7 @@ def __init__(
brush=brush,
format=format,
layers=layers,
canvas_size=canvas_size,
)


Expand Down Expand Up @@ -257,6 +261,7 @@ def __init__(
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")
Expand Down Expand Up @@ -288,6 +293,7 @@ def __init__(
brush=brush,
format=format,
layers=layers,
canvas_size=canvas_size,
)


Expand Down
2 changes: 2 additions & 0 deletions js/imageeditor/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -175,6 +176,7 @@
/>

<InteractiveImageEditor
{canvas_size}
on:change={() => handle_history_change()}
bind:image_id
{crop_size}
Expand Down
19 changes: 15 additions & 4 deletions js/imageeditor/shared/ImageEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -73,8 +73,11 @@
change: void;
}>();
export let crop_constraint = false;
export let canvas_size: [number, number] | undefined;

let dimensions = writable(crop_size);
$: orig_canvas_size = canvas_size;

let dimensions = writable(canvas_size || [800, 600]);
export let height = 0;

let editor_box: EditorContext["editor_box"] = writable({
Expand Down Expand Up @@ -260,7 +263,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 {
Expand All @@ -270,7 +276,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);
Expand Down
3 changes: 3 additions & 0 deletions js/imageeditor/shared/InteractiveImageEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand Down Expand Up @@ -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}
Expand Down
15 changes: 9 additions & 6 deletions js/imageeditor/shared/utils/pixi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions test/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ def test_component_functions(self):
"server_fns": ["accept_blobs"],
"format": "webp",
"layers": True,
"canvas_size": None,
}

def test_process_example(self):
Expand Down