Skip to content

Commit

Permalink
Add min/max-imize button to gr.Image and gr.Gallery (gradio-app#8964)
Browse files Browse the repository at this point in the history
* add max/min-imize and zoom in and out to image preview

* add full screen icon to gallery

* add stories

* add changeset

* use native full screen api

* remove zoom in/out

* add changeset

* tweaks

* remove zoom prop

* fix ui test

* add annotated image btns

* add changeset

* format

* ruff format

* fix test

* remove

* tweak

* fix test

* format

* amend bool check

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: pngwn <hello@pngwn.io>
  • Loading branch information
3 people authored Aug 5, 2024
1 parent a2e36c3 commit bf6bbd9
Show file tree
Hide file tree
Showing 20 changed files with 413 additions and 137 deletions.
10 changes: 10 additions & 0 deletions .changeset/legal-areas-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@gradio/annotatedimage": minor
"@gradio/gallery": minor
"@gradio/icons": minor
"@gradio/image": minor
"@gradio/imageeditor": minor
"gradio": minor
---

feat:Add min/max-imize button to gr.Image and gr.Gallery
3 changes: 3 additions & 0 deletions gradio/components/annotated_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def __init__(
elem_classes: list[str] | str | None = None,
render: bool = True,
key: int | str | None = None,
show_fullscreen_button: bool = True,
):
"""
Parameters:
Expand All @@ -92,12 +93,14 @@ 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.
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.
show_fullscreen_button: If True, will show a button to allow the image to be viewed in fullscreen mode.
"""
self.format = format
self.show_legend = show_legend
self.height = height
self.width = width
self.color_map = color_map
self.show_fullscreen_button = show_fullscreen_button
super().__init__(
label=label,
every=every,
Expand Down
3 changes: 3 additions & 0 deletions gradio/components/gallery.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(
show_download_button: bool | None = True,
interactive: bool | None = None,
type: Literal["numpy", "pil", "filepath"] = "filepath",
show_fullscreen_button: bool = True,
):
"""
Parameters:
Expand Down Expand Up @@ -119,6 +120,7 @@ def __init__(
show_download_button: If True, will show a download button in the corner of the selected image. If False, the icon does not appear. Default is True.
interactive: If True, the gallery will be interactive, allowing the user to upload images. If False, the gallery will be static. Default is True.
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. If the image is SVG, the `type` is ignored and the filepath of the SVG is returned.
show_fullscreen_button: If True, will show a fullscreen icon in the corner of the component that allows user to view the gallery in fullscreen mode. 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.format = format
self.columns = columns
Expand All @@ -134,6 +136,7 @@ def __init__(
)
self.selected_index = selected_index
self.type = type
self.show_fullscreen_button = show_fullscreen_button

self.show_share_button = (
(utils.get_space() is not None)
Expand Down
3 changes: 3 additions & 0 deletions gradio/components/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def __init__(
key: int | str | None = None,
mirror_webcam: bool = True,
show_share_button: bool | None = None,
show_fullscreen_button: bool = True,
):
"""
Parameters:
Expand Down Expand Up @@ -101,6 +102,7 @@ def __init__(
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.
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
self.mirror_webcam = mirror_webcam
Expand Down Expand Up @@ -138,6 +140,7 @@ def __init__(
if show_share_button is None
else show_share_button
)
self.show_fullscreen_button = show_fullscreen_button
super().__init__(
label=label,
every=every,
Expand Down
3 changes: 3 additions & 0 deletions gradio/components/image_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def __init__(
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
show_fullscreen_button: bool = True,
):
"""
Parameters:
Expand Down Expand Up @@ -213,6 +214,7 @@ def __init__(
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.
show_fullscreen_button: If True, will display button to view image in fullscreen mode.
"""
self._selectable = _selectable
self.mirror_webcam = mirror_webcam
Expand Down Expand Up @@ -254,6 +256,7 @@ def __init__(
self.format = format
self.layers = layers
self.canvas_size = canvas_size
self.show_fullscreen_button = show_fullscreen_button

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 @@ -125,6 +125,7 @@ def __init__(
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
show_fullscreen_button: bool = True,
):
if not brush:
brush = Brush(colors=["#000000"], color_mode="fixed")
Expand Down Expand Up @@ -159,6 +160,7 @@ def __init__(
format=format,
layers=layers,
canvas_size=canvas_size,
show_fullscreen_button=show_fullscreen_button,
)


Expand Down Expand Up @@ -207,6 +209,7 @@ def __init__(
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
show_fullscreen_button: bool = True,
):
super().__init__(
value=value,
Expand Down Expand Up @@ -239,6 +242,7 @@ def __init__(
format=format,
layers=layers,
canvas_size=canvas_size,
show_fullscreen_button=show_fullscreen_button,
)


Expand Down Expand Up @@ -291,6 +295,7 @@ def __init__(
format: str = "webp",
layers: bool = True,
canvas_size: tuple[int, int] | None = None,
show_fullscreen_button: bool = True,
):
if not brush:
brush = Brush(colors=["#000000"], color_mode="fixed")
Expand Down Expand Up @@ -325,6 +330,7 @@ def __init__(
format=format,
layers=layers,
canvas_size=canvas_size,
show_fullscreen_button=show_fullscreen_button,
)


Expand Down
58 changes: 53 additions & 5 deletions js/annotatedimage/Index.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import type { Gradio, SelectData } from "@gradio/utils";
import { Block, BlockLabel, Empty } from "@gradio/atoms";
import { Image } from "@gradio/icons";
import { onMount } from "svelte";
import { Block, BlockLabel, Empty, IconButton } from "@gradio/atoms";
import { Image, Maximize, Minimize } from "@gradio/icons";
import { StatusTracker } from "@gradio/statustracker";
import type { LoadingStatus } from "@gradio/statustracker";
import { type FileData } from "@gradio/client";
Expand Down Expand Up @@ -38,6 +39,24 @@
export let min_width: number | undefined = undefined;
let active: string | null = null;
export let loading_status: LoadingStatus;
export let show_fullscreen_button = true;
let is_full_screen = false;
let image_container: HTMLElement;
onMount(() => {
document.addEventListener("fullscreenchange", () => {
is_full_screen = !!document.fullscreenElement;
});
});
const toggle_full_screen = async (): Promise<void> => {
if (!is_full_screen) {
await image_container.requestFullscreen();
} else {
await document.exitFullscreen();
}
};
// `value` can be updated before the Promises from `resolve_wasm_src` are resolved.
// In such a case, the resolved values for the old `value` have to be discarded,
Expand Down Expand Up @@ -139,17 +158,36 @@
{#if _value == null}
<Empty size="large" unpadded_box={true}><Image /></Empty>
{:else}
<div class="image-container">
<div class="image-container" bind:this={image_container}>
<div class="icon-buttons">
{#if !is_full_screen && show_fullscreen_button}
<IconButton
Icon={Maximize}
label="View in full screen"
on:click={toggle_full_screen}
/>
{/if}

{#if is_full_screen}
<IconButton
Icon={Minimize}
label="Exit full screen"
on:click={toggle_full_screen}
/>
{/if}
</div>

<img
class="base-image"
class:fit-height={height}
class:fit-height={height && !is_full_screen}
src={_value ? _value.image.url : null}
alt="the base file that is annotated"
/>
{#each _value ? _value?.annotations : [] as ann, i}
<img
alt="segmentation mask identifying {label} within the uploaded file"
class="mask fit-height"
class:fit-height={!is_full_screen}
class:active={active == ann.label}
class:inactive={active != ann.label && active != null}
src={ann.image.url}
Expand Down Expand Up @@ -191,6 +229,7 @@
display: block;
width: 100%;
height: auto;
position: absolute;
}
.container {
display: flex;
Expand All @@ -210,7 +249,6 @@
overflow: hidden;
}
.fit-height {
position: absolute;
top: 0;
left: 0;
width: 100%;
Expand All @@ -220,6 +258,7 @@
.mask {
opacity: 0.85;
transition: all 0.2s ease-in-out;
position: absolute;
}
.image-container:hover .mask {
opacity: 0.3;
Expand Down Expand Up @@ -248,4 +287,13 @@
border-radius: var(--radius-sm);
padding: var(--spacing-sm);
}
.icon-buttons {
display: flex;
position: absolute;
top: 6px;
right: 6px;
gap: var(--size-1);
z-index: 1;
}
</style>
12 changes: 12 additions & 0 deletions js/gallery/Gallery.stories.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { Template, Story } from "@storybook/addon-svelte-csf";
import Gallery from "./Index.svelte";
import { allModes } from "../storybook/modes";
import { within } from "@testing-library/dom";
import { userEvent } from "@storybook/test";
export const meta = {
title: "Components/Gallery",
Expand Down Expand Up @@ -134,6 +136,16 @@
<Story
name="Gallery with label"
args={{ label: "My Cheetah Gallery", show_label: true }}
play={async ({ canvasElement }) => {
const canvas = within(canvasElement);

const image = canvas.getByLabelText("Thumbnail 1 of 7");
await userEvent.click(image);
const expand_btn = canvas.getByRole("button", {
name: "View in full screen"
});
await userEvent.click(expand_btn);
}}
/>
<Story
name="Gallery without label"
Expand Down
2 changes: 2 additions & 0 deletions js/gallery/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
prop_change: Record<string, any>;
clear_status: LoadingStatus;
}>;
export let show_fullscreen_button = true;
const dispatch = createEventDispatcher();
Expand Down Expand Up @@ -113,6 +114,7 @@
{show_download_button}
i18n={gradio.i18n}
_fetch={gradio.client.fetch}
{show_fullscreen_button}
/>
{/if}
</Block>
Loading

0 comments on commit bf6bbd9

Please sign in to comment.