| <script lang="ts"> | |
| import { getContext, onMount, tick, createEventDispatcher } from "svelte"; | |
| import { type ToolContext, TOOL_KEY } from "./Tools.svelte"; | |
| import { type EditorContext, EDITOR_KEY } from "../ImageEditor.svelte"; | |
| import { | |
| Image as ImageIcon, | |
| Webcam as WebcamIcon, | |
| ImagePaste | |
| } from "@gradio/icons"; | |
| import { Upload } from "@gradio/upload"; | |
| import { Webcam } from "@gradio/image"; | |
| import { type I18nFormatter } from "@gradio/utils"; | |
| import { IconButton } from "@gradio/atoms"; | |
| import { type Client } from "@gradio/client"; | |
| import { add_bg_color, add_bg_image } from "./sources"; | |
| import type { FileData } from "@gradio/client"; | |
| export let background_file: FileData | null; | |
| export let root: string; | |
| export let sources: ("upload" | "webcam" | "clipboard")[] = [ | |
| "upload", | |
| "webcam", | |
| "clipboard" | |
| ]; | |
| export let mirror_webcam = true; | |
| export let i18n: I18nFormatter; | |
| export let upload: Client["upload"]; | |
| export let stream_handler: Client["stream"]; | |
| const { active_tool } = getContext<ToolContext>(TOOL_KEY); | |
| const { pixi, dimensions, register_context, reset, editor_box } = | |
| getContext<EditorContext>(EDITOR_KEY); | |
| export let active_mode: "webcam" | "color" | null = null; | |
| let background: Blob | File | null; | |
| const dispatch = createEventDispatcher<{ | |
| upload: never; | |
| }>(); | |
| const sources_meta = { | |
| upload: { | |
| icon: ImageIcon, | |
| label: "Upload", | |
| order: 0, | |
| id: "bg_upload", | |
| cb() { | |
| upload_component.open_file_upload(); | |
| $active_tool = "bg"; | |
| } | |
| }, | |
| webcam: { | |
| icon: WebcamIcon, | |
| label: "Webcam", | |
| order: 1, | |
| id: "bg_webcam", | |
| cb() { | |
| active_mode = "webcam"; | |
| $active_tool = "bg"; | |
| } | |
| }, | |
| clipboard: { | |
| icon: ImagePaste, | |
| label: "Paste", | |
| order: 2, | |
| id: "bg_clipboard", | |
| cb() { | |
| process_clipboard(); | |
| $active_tool = null; | |
| } | |
| } | |
| } as const; | |
| $: sources_list = sources | |
| .map((src) => sources_meta[src]) | |
| .sort((a, b) => a.order - b.order); | |
| let upload_component: Upload; | |
| async function process_clipboard(): Promise<void> { | |
| const items = await navigator.clipboard.read(); | |
| for (let i = 0; i < items.length; i++) { | |
| const type = items[i].types.find((t) => t.startsWith("image/")); | |
| if (type) { | |
| const blob = await items[i].getType(type); | |
| background = blob || null; | |
| } | |
| } | |
| } | |
| function handle_upload(e: CustomEvent<Blob | any>): void { | |
| const file_data = e.detail; | |
| background = file_data; | |
| active_mode = null; | |
| } | |
| let should_reset = true; | |
| async function set_background(): Promise<void> { | |
| if (!$pixi) return; | |
| if (background) { | |
| const add_image = add_bg_image( | |
| $pixi.background_container, | |
| $pixi.renderer, | |
| background, | |
| $pixi.resize | |
| ); | |
| $dimensions = await add_image.start(); | |
| if (should_reset) { | |
| reset(false, $dimensions); | |
| } | |
| add_image.execute(); | |
| should_reset = true; | |
| await tick(); | |
| bg = true; | |
| } | |
| } | |
| async function process_bg_file(file: FileData | null): Promise<void> { | |
| if (!file || !file.url) return; | |
| should_reset = false; | |
| const blob_res = await fetch(file.url); | |
| const blob = await blob_res.blob(); | |
| background = blob; | |
| } | |
| function handle_key(e: KeyboardEvent): void { | |
| if (e.key === "Escape") { | |
| active_mode = null; | |
| } | |
| } | |
| $: background && set_background(); | |
| $: process_bg_file(background_file); | |
| export let bg = false; | |
| register_context("bg", { | |
| init_fn: () => { | |
| if (!$pixi) return; | |
| const add_image = add_bg_color( | |
| $pixi.background_container, | |
| $pixi.renderer, | |
| "black", | |
| ...$dimensions, | |
| $pixi.resize | |
| ); | |
| $dimensions = add_image.start(); | |
| add_image.execute(); | |
| }, | |
| reset_fn: () => {} | |
| }); | |
| </script> | |
| <svelte:window on:keydown={handle_key} /> | |
| {#if sources.length} | |
| <div class="source-wrap"> | |
| {#each sources_list as { icon, label, id, cb } (id)} | |
| <IconButton | |
| Icon={icon} | |
| size="medium" | |
| padded={false} | |
| label={label + " button"} | |
| hasPopup={true} | |
| transparent={true} | |
| on:click={cb} | |
| /> | |
| {/each} | |
| <span class="sep"></span> | |
| </div> | |
| <div class="upload-container"> | |
| <Upload | |
| hidden={true} | |
| bind:this={upload_component} | |
| filetype="image/*" | |
| on:load={handle_upload} | |
| on:error | |
| {root} | |
| disable_click={!sources.includes("upload")} | |
| format="blob" | |
| {upload} | |
| {stream_handler} | |
| ></Upload> | |
| {#if active_mode === "webcam"} | |
| <div | |
| class="modal" | |
| style:max-width="{$editor_box.child_width}px" | |
| style:max-height="{$editor_box.child_height}px" | |
| style:top="{$editor_box.child_top - $editor_box.parent_top}px" | |
| > | |
| <div class="modal-inner"> | |
| <Webcam | |
| {upload} | |
| {root} | |
| on:capture={handle_upload} | |
| on:error | |
| on:drag | |
| {mirror_webcam} | |
| streaming={false} | |
| mode="image" | |
| include_audio={false} | |
| {i18n} | |
| /> | |
| </div> | |
| </div> | |
| {/if} | |
| </div> | |
| {/if} | |
| <style> | |
| .modal { | |
| position: absolute; | |
| height: 100%; | |
| width: 100%; | |
| left: 0; | |
| right: 0; | |
| margin: auto; | |
| z-index: var(--layer-top); | |
| display: flex; | |
| align-items: center; | |
| } | |
| .modal-inner { | |
| width: 100%; | |
| } | |
| .sep { | |
| height: 12px; | |
| background-color: var(--block-border-color); | |
| width: 1px; | |
| display: block; | |
| margin-left: var(--spacing-xl); | |
| } | |
| .source-wrap { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| margin-left: var(--spacing-lg); | |
| height: 100%; | |
| } | |
| </style> | |