|
<script lang="ts" context="module"> |
|
export interface EditorData { |
|
background: FileData | null; |
|
layers: FileData[] | null; |
|
composite: FileData | null; |
|
} |
|
|
|
export interface ImageBlobs { |
|
background: FileData | null; |
|
layers: FileData[]; |
|
composite: FileData | null; |
|
} |
|
</script> |
|
|
|
<script lang="ts"> |
|
import { type I18nFormatter } from "@gradio/utils"; |
|
import { |
|
prepare_files, |
|
upload, |
|
normalise_file, |
|
type FileData |
|
} from "@gradio/client"; |
|
|
|
import ImageEditor from "./ImageEditor.svelte"; |
|
import Layers from "./layers/Layers.svelte"; |
|
import { type Brush as IBrush } from "./tools/Brush.svelte"; |
|
import { type Eraser } from "./tools/Brush.svelte"; |
|
|
|
export let brush: IBrush | null; |
|
export let eraser: Eraser | null; |
|
import { Tools, Crop, Brush, Sources } from "./tools"; |
|
|
|
export let sources: ("clipboard" | "webcam" | "upload")[]; |
|
export let crop_size: [number, number] | `${string}:${string}` | null = null; |
|
export let i18n: I18nFormatter; |
|
export let root: string; |
|
export let proxy_url: string; |
|
export let changeable = false; |
|
export let value: EditorData | null = { |
|
background: null, |
|
layers: [], |
|
composite: null |
|
}; |
|
export let transforms: "crop"[] = ["crop"]; |
|
|
|
let editor: ImageEditor; |
|
|
|
function is_not_null(o: Blob | null): o is Blob { |
|
return !!o; |
|
} |
|
|
|
function is_file_data(o: null | FileData): o is FileData { |
|
return !!o; |
|
} |
|
|
|
export async function get_data(): Promise<ImageBlobs> { |
|
const blobs = await editor.get_blobs(); |
|
|
|
const bg = blobs.background |
|
? upload( |
|
await prepare_files([new File([blobs.background], "background.png")]), |
|
root |
|
) |
|
: Promise.resolve(null); |
|
|
|
const layers = blobs.layers |
|
.filter(is_not_null) |
|
.map(async (blob, i) => |
|
upload(await prepare_files([new File([blob], `layer_${i}.png`)]), root) |
|
); |
|
|
|
const composite = blobs.composite |
|
? upload( |
|
await prepare_files([new File([blobs.composite], "composite.png")]), |
|
root |
|
) |
|
: Promise.resolve(null); |
|
|
|
const [background, composite_, ...layers_] = await Promise.all([ |
|
bg, |
|
composite, |
|
...layers |
|
]); |
|
|
|
return { |
|
background: Array.isArray(background) ? background[0] : background, |
|
layers: layers_ |
|
.flatMap((layer) => (Array.isArray(layer) ? layer : [layer])) |
|
.filter(is_file_data), |
|
composite: Array.isArray(composite_) ? composite_[0] : composite_ |
|
}; |
|
} |
|
|
|
function handle_value(value: EditorData | null): void { |
|
if (!editor) return; |
|
if (value == null) { |
|
editor.handle_remove(); |
|
} |
|
} |
|
|
|
$: handle_value(value); |
|
|
|
$: crop_constraint = crop_size; |
|
let bg = false; |
|
let history = false; |
|
|
|
$: editor && |
|
editor.set_tool && |
|
(sources && sources.length |
|
? editor.set_tool("bg") |
|
: editor.set_tool("draw")); |
|
</script> |
|
|
|
<ImageEditor |
|
bind:this={editor} |
|
{changeable} |
|
on:save |
|
bind:history |
|
bind:bg |
|
{sources} |
|
crop_constraint={!!crop_constraint} |
|
> |
|
<Tools {i18n}> |
|
{#if sources && sources.length} |
|
<Sources |
|
{i18n} |
|
{root} |
|
{sources} |
|
bind:bg |
|
background_file={normalise_file( |
|
value?.background || null, |
|
root, |
|
proxy_url |
|
)} |
|
></Sources> |
|
{/if} |
|
{#if transforms.includes("crop")} |
|
<Crop {crop_constraint} /> |
|
{/if} |
|
{#if brush} |
|
<Brush |
|
color_mode={brush.color_mode} |
|
default_color={brush.default_color} |
|
default_size={brush.default_size} |
|
colors={brush.colors} |
|
mode="draw" |
|
/> |
|
{/if} |
|
|
|
{#if brush && eraser} |
|
<Brush default_size={eraser.default_size} mode="erase" /> |
|
{/if} |
|
</Tools> |
|
|
|
<Layers |
|
layer_files={normalise_file(value?.layers || null, root, proxy_url)} |
|
/> |
|
|
|
{#if !bg && !history} |
|
<div class="empty wrap"> |
|
{#if sources && sources.length} |
|
<div>Upload an image</div> |
|
{/if} |
|
|
|
{#if sources && sources.length && brush} |
|
<div class="or">or</div> |
|
{/if} |
|
{#if brush} |
|
<div>select the draw tool to start</div> |
|
{/if} |
|
</div> |
|
{/if} |
|
</ImageEditor> |
|
|
|
<style> |
|
.empty { |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
|
|
position: absolute; |
|
height: 100%; |
|
width: 100%; |
|
left: 0; |
|
right: 0; |
|
margin: auto; |
|
z-index: var(--layer-top); |
|
text-align: center; |
|
color: var(--body-text-color); |
|
} |
|
|
|
.wrap { |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
min-height: var(--size-60); |
|
color: var(--block-label-text-color); |
|
line-height: var(--line-md); |
|
height: 100%; |
|
padding-top: var(--size-3); |
|
font-size: var(--text-lg); |
|
pointer-events: none; |
|
transform: translateY(-30px); |
|
} |
|
|
|
.or { |
|
color: var(--body-text-color-subdued); |
|
} |
|
</style> |
|
|