| <script lang="ts"> | |
| import { getContext, onMount, tick } from "svelte"; | |
| import { type ToolContext, TOOL_KEY } from "./Tools.svelte"; | |
| import { type EditorContext, EDITOR_KEY } from "../ImageEditor.svelte"; | |
| import { | |
| Upload as UploadIcon, | |
| Webcam as WebcamIcon, | |
| ImagePaste | |
| } from "@gradio/icons"; | |
| import { Upload } from "@gradio/upload"; | |
| import { Webcam } from "@gradio/image"; | |
| import { type I18nFormatter } from "@gradio/utils"; | |
| import { add_bg_color, add_bg_image } from "./sources"; | |
| import type { FileData, normalise_file } 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; | |
| const { active_tool, register_tool } = getContext<ToolContext>(TOOL_KEY); | |
| const { pixi, dimensions, register_context, reset, editor_box } = | |
| getContext<EditorContext>(EDITOR_KEY); | |
| let active_mode: "webcam" | "color" | null = null; | |
| let background: Blob | File | null; | |
| const sources_meta = { | |
| upload: { | |
| icon: UploadIcon, | |
| label: "Upload", | |
| order: 0, | |
| id: "bg_upload", | |
| cb() { | |
| upload.open_file_upload(); | |
| } | |
| }, | |
| webcam: { | |
| icon: WebcamIcon, | |
| label: "Webcam", | |
| order: 1, | |
| id: "bg_webcam", | |
| cb() { | |
| active_mode = "webcam"; | |
| } | |
| }, | |
| clipboard: { | |
| icon: ImagePaste, | |
| label: "Paste", | |
| order: 2, | |
| id: "bg_clipboard", | |
| cb() { | |
| process_clipboard(); | |
| } | |
| } | |
| } as const; | |
| $: sources_list = sources | |
| .map((src) => sources_meta[src]) | |
| .sort((a, b) => a.order - b.order); | |
| let upload: 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: () => {} | |
| }); | |
| onMount(() => { | |
| return register_tool("bg", { | |
| default: "bg_upload", | |
| options: sources_list || [] | |
| }); | |
| }); | |
| </script> | |
| <svelte:window on:keydown={handle_key} /> | |
| {#if $active_tool === "bg"} | |
| <div class="upload-container"> | |
| <Upload | |
| hidden={true} | |
| bind:this={upload} | |
| filetype="image/*" | |
| on:load={handle_upload} | |
| on:error | |
| {root} | |
| disable_click={!sources.includes("upload")} | |
| format="blob" | |
| ></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 | |
| {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; | |
| background-color: rgba(0, 0, 0, 0.9); | |
| margin: auto; | |
| z-index: var(--layer-top); | |
| display: flex; | |
| align-items: center; | |
| } | |
| .modal-inner { | |
| width: 100%; | |
| } | |
| </style> | |