| <script lang="ts"> | |
| import { createEventDispatcher, tick } from "svelte"; | |
| import { Download, Image as ImageIcon } from "@gradio/icons"; | |
| import { DownloadLink } from "@gradio/wasm/svelte"; | |
| import { uploadToHuggingFace } from "@gradio/utils"; | |
| import { BlockLabel, IconButton, ShareButton, SelectSource} from "@gradio/atoms"; | |
| import { Upload } from "@gradio/upload"; | |
| import { Webcam } from "@gradio/image"; | |
| import type { FileData, Client } from "@gradio/client"; | |
| import type { I18nFormatter, SelectData } from "@gradio/utils"; | |
| import { Clear } from "@gradio/icons"; | |
| import ImageCanvas from "./ImageCanvas.svelte"; | |
| import AnnotatedImageData from "./AnnotatedImageData"; | |
| type source_type = "upload" | "webcam" | "clipboard" | null; | |
| export let value: null | AnnotatedImageData; | |
| export let label: string | undefined = undefined; | |
| export let show_label: boolean; | |
| export let sources: source_type[] = ["upload", "webcam", "clipboard"]; | |
| export let selectable = false; | |
| export let root: string; | |
| export let interactive: boolean; | |
| export let i18n: I18nFormatter; | |
| export let showShareButton: boolean; | |
| export let showDownloadButton: boolean; | |
| export let showClearButton: boolean; | |
| export let boxesAlpha; | |
| export let labelList: string[]; | |
| export let labelColors: string[]; | |
| export let boxMinSize: number; | |
| export let handleSize: number; | |
| export let height: number | string; | |
| export let width: number | string; | |
| export let boxThickness: number; | |
| export let disableEditBoxes: boolean; | |
| export let singleBox: boolean; | |
| export let showRemoveButton: boolean; | |
| export let handlesCursor: boolean; | |
| export let boxSelectedThickness: number; | |
| export let max_file_size: number | null = null; | |
| export let cli_upload: Client["upload"]; | |
| export let stream_handler: Client["stream_factory"]; | |
| let upload: Upload; | |
| let uploading = false; | |
| export let active_source: source_type = null; | |
| function handle_upload({ detail }: CustomEvent<FileData>): void { | |
| value = new AnnotatedImageData(); | |
| value.image = detail; | |
| dispatch("upload"); | |
| } | |
| async function handle_save(img_blob: Blob | any): Promise<void> { | |
| const f = await upload.load_files([new File([img_blob], `webcam.png`)]); | |
| const image = f?.[0] || null; | |
| if (image) { | |
| value = new AnnotatedImageData(); | |
| value.image = image; | |
| } else { | |
| value = null; | |
| } | |
| await tick(); | |
| dispatch("change"); | |
| } | |
| $: if (uploading) clear(); | |
| const dispatch = createEventDispatcher<{ | |
| change: undefined; | |
| clear: undefined; | |
| drag: boolean; | |
| upload?: never; | |
| select: SelectData; | |
| }>(); | |
| let dragging = false; | |
| $: dispatch("drag", dragging); | |
| $: if (!active_source && sources) { | |
| active_source = sources[0]; | |
| } | |
| async function handle_select_source( | |
| source: (typeof sources)[number] | |
| ): Promise<void> { | |
| switch (source) { | |
| case "clipboard": | |
| upload.paste_clipboard(); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| function clear() { | |
| value = null; | |
| dispatch("clear"); | |
| dispatch("change"); | |
| } | |
| </script> | |
| <BlockLabel {show_label} Icon={ImageIcon} label={label || "Image Annotator"} /> | |
| <div class="icon-buttons"> | |
| {#if showDownloadButton && value !== null} | |
| <DownloadLink href={value.image.url} download={value.image.orig_name || "image"}> | |
| <IconButton Icon={Download} label={i18n("common.download")} /> | |
| </DownloadLink> | |
| {/if} | |
| {#if showShareButton && value !== null} | |
| <ShareButton | |
| {i18n} | |
| on:share | |
| on:error | |
| formatter={async (value) => { | |
| if (value === null) return ""; | |
| let url = await uploadToHuggingFace(value.image, "base64"); | |
| return `<img src="${url}" />`; | |
| }} | |
| {value} | |
| /> | |
| {/if} | |
| {#if showClearButton && value !== null && interactive} | |
| <div> | |
| <IconButton | |
| Icon={Clear} | |
| label="Remove Image" | |
| on:click={clear} | |
| /> | |
| </div> | |
| {/if} | |
| </div> | |
| <div data-testid="image" class="image-container"> | |
| <div class="upload-container"> | |
| <Upload | |
| hidden={value !== null || active_source === "webcam"} | |
| bind:this={upload} | |
| bind:uploading | |
| bind:dragging | |
| filetype={active_source === "clipboard" ? "clipboard" : "image/*"} | |
| on:load={handle_upload} | |
| on:error | |
| {root} | |
| {max_file_size} | |
| disable_click={!sources.includes("upload")} | |
| upload={cli_upload} | |
| {stream_handler} | |
| > | |
| {#if value === null} | |
| <slot /> | |
| {/if} | |
| </Upload> | |
| {#if value === null && active_source === "webcam"} | |
| <Webcam | |
| {root} | |
| on:capture={(e) => handle_save(e.detail)} | |
| on:stream={(e) => handle_save(e.detail)} | |
| on:error | |
| on:drag | |
| on:upload={(e) => handle_save(e.detail)} | |
| mode="image" | |
| include_audio={false} | |
| {i18n} | |
| {upload} | |
| /> | |
| {/if} | |
| {#if value !== null} | |
| <div class:selectable class="image-frame"> | |
| <ImageCanvas | |
| bind:value | |
| on:change={() => dispatch("change")} | |
| {height} | |
| {width} | |
| {boxesAlpha} | |
| {labelList} | |
| {labelColors} | |
| {boxMinSize} | |
| {interactive} | |
| {handleSize} | |
| {boxThickness} | |
| {singleBox} | |
| {disableEditBoxes} | |
| {showRemoveButton} | |
| {handlesCursor} | |
| {boxSelectedThickness} | |
| src={value.image.url} | |
| /> | |
| </div> | |
| {/if} | |
| </div> | |
| {#if (sources.length > 1 || sources.includes("clipboard")) && value === null && interactive} | |
| <SelectSource | |
| {sources} | |
| bind:active_source | |
| handle_clear={clear} | |
| handle_select={handle_select_source} | |
| /> | |
| {/if} | |
| </div> | |
| <style> | |
| .image-frame :global(img) { | |
| width: var(--size-full); | |
| height: var(--size-full); | |
| object-fit: cover; | |
| } | |
| .image-frame { | |
| object-fit: cover; | |
| width: 100%; | |
| } | |
| .upload-container { | |
| height: 100%; | |
| width: 100%; | |
| flex-shrink: 1; | |
| max-height: 100%; | |
| } | |
| .image-container { | |
| display: flex; | |
| height: 100%; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| max-height: 100%; | |
| } | |
| .selectable { | |
| cursor: crosshair; | |
| } | |
| .icon-buttons { | |
| display: flex; | |
| position: absolute; | |
| top: 6px; | |
| right: 6px; | |
| gap: var(--size-1); | |
| } | |
| </style> | |