@gradio/image 0.3.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/Image.stories.svelte +50 -0
- package/Image.test.ts +61 -0
- package/LICENSE +201 -0
- package/README.md +11 -0
- package/example/Image.svelte +41 -0
- package/example/index.ts +1 -0
- package/interactive/Cropper.svelte +33 -0
- package/interactive/Image.svelte +456 -0
- package/interactive/InteractiveImage.svelte +108 -0
- package/interactive/ModifySketch.svelte +45 -0
- package/interactive/Sketch.svelte +626 -0
- package/interactive/SketchSettings.svelte +77 -0
- package/interactive/Webcam.svelte +204 -0
- package/interactive/index.ts +2 -0
- package/package.json +27 -0
- package/shared/utils.ts +24 -0
- package/src/Image.svelte +0 -0
- package/static/ImagePreview.svelte +95 -0
- package/static/StaticImage.svelte +76 -0
- package/static/index.ts +1 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { createEventDispatcher, onMount } from "svelte";
|
3
|
+
import { Camera, Circle, Square } from "@gradio/icons";
|
4
|
+
import type { I18nFormatter } from "js/utils/src";
|
5
|
+
|
6
|
+
let video_source: HTMLVideoElement;
|
7
|
+
let canvas: HTMLCanvasElement;
|
8
|
+
export let streaming = false;
|
9
|
+
export let pending = false;
|
10
|
+
|
11
|
+
export let mode: "image" | "video" = "image";
|
12
|
+
export let mirror_webcam: boolean;
|
13
|
+
export let include_audio: boolean;
|
14
|
+
export let i18n: I18nFormatter;
|
15
|
+
|
16
|
+
const dispatch = createEventDispatcher<{
|
17
|
+
stream: undefined;
|
18
|
+
capture:
|
19
|
+
| {
|
20
|
+
data: FileReader["result"];
|
21
|
+
name: string;
|
22
|
+
is_example?: boolean;
|
23
|
+
is_file: boolean;
|
24
|
+
}
|
25
|
+
| string;
|
26
|
+
error: string;
|
27
|
+
start_recording: undefined;
|
28
|
+
stop_recording: undefined;
|
29
|
+
}>();
|
30
|
+
|
31
|
+
onMount(() => (canvas = document.createElement("canvas")));
|
32
|
+
|
33
|
+
async function access_webcam(): Promise<void> {
|
34
|
+
try {
|
35
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
36
|
+
video: true,
|
37
|
+
audio: include_audio
|
38
|
+
});
|
39
|
+
video_source.srcObject = stream;
|
40
|
+
video_source.muted = true;
|
41
|
+
video_source.play();
|
42
|
+
} catch (err) {
|
43
|
+
if (err instanceof DOMException && err.name == "NotAllowedError") {
|
44
|
+
dispatch("error", i18n("image.allow_webcam_access"));
|
45
|
+
} else {
|
46
|
+
throw err;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function take_picture(): void {
|
52
|
+
var context = canvas.getContext("2d")!;
|
53
|
+
|
54
|
+
if (video_source.videoWidth && video_source.videoHeight) {
|
55
|
+
canvas.width = video_source.videoWidth;
|
56
|
+
canvas.height = video_source.videoHeight;
|
57
|
+
context.drawImage(
|
58
|
+
video_source,
|
59
|
+
0,
|
60
|
+
0,
|
61
|
+
video_source.videoWidth,
|
62
|
+
video_source.videoHeight
|
63
|
+
);
|
64
|
+
|
65
|
+
var data = canvas.toDataURL("image/png");
|
66
|
+
dispatch(streaming ? "stream" : "capture", data);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
let recording = false;
|
71
|
+
let recorded_blobs: BlobPart[] = [];
|
72
|
+
let stream: MediaStream;
|
73
|
+
let mimeType: string;
|
74
|
+
let media_recorder: MediaRecorder;
|
75
|
+
|
76
|
+
function take_recording(): void {
|
77
|
+
if (recording) {
|
78
|
+
media_recorder.stop();
|
79
|
+
let video_blob = new Blob(recorded_blobs, { type: mimeType });
|
80
|
+
let ReaderObj = new FileReader();
|
81
|
+
ReaderObj.onload = function (e): void {
|
82
|
+
if (e.target) {
|
83
|
+
dispatch("capture", {
|
84
|
+
data: e.target.result,
|
85
|
+
name: "sample." + mimeType.substring(6),
|
86
|
+
is_example: false,
|
87
|
+
is_file: false
|
88
|
+
});
|
89
|
+
dispatch("stop_recording");
|
90
|
+
}
|
91
|
+
};
|
92
|
+
ReaderObj.readAsDataURL(video_blob);
|
93
|
+
} else {
|
94
|
+
dispatch("start_recording");
|
95
|
+
recorded_blobs = [];
|
96
|
+
let validMimeTypes = ["video/webm", "video/mp4"];
|
97
|
+
for (let validMimeType of validMimeTypes) {
|
98
|
+
if (MediaRecorder.isTypeSupported(validMimeType)) {
|
99
|
+
mimeType = validMimeType;
|
100
|
+
break;
|
101
|
+
}
|
102
|
+
}
|
103
|
+
if (mimeType === null) {
|
104
|
+
console.error("No supported MediaRecorder mimeType");
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
media_recorder = new MediaRecorder(stream, {
|
108
|
+
mimeType: mimeType
|
109
|
+
});
|
110
|
+
media_recorder.addEventListener("dataavailable", function (e) {
|
111
|
+
recorded_blobs.push(e.data);
|
112
|
+
});
|
113
|
+
media_recorder.start(200);
|
114
|
+
}
|
115
|
+
recording = !recording;
|
116
|
+
}
|
117
|
+
|
118
|
+
access_webcam();
|
119
|
+
|
120
|
+
if (streaming && mode === "image") {
|
121
|
+
window.setInterval(() => {
|
122
|
+
if (video_source && !pending) {
|
123
|
+
take_picture();
|
124
|
+
}
|
125
|
+
}, 500);
|
126
|
+
}
|
127
|
+
</script>
|
128
|
+
|
129
|
+
<div class="wrap">
|
130
|
+
<!-- svelte-ignore a11y-media-has-caption -->
|
131
|
+
<video bind:this={video_source} class:flip={mirror_webcam} />
|
132
|
+
{#if !streaming}
|
133
|
+
<button on:click={mode === "image" ? take_picture : take_recording}>
|
134
|
+
{#if mode === "video"}
|
135
|
+
{#if recording}
|
136
|
+
<div class="icon">
|
137
|
+
<Square />
|
138
|
+
</div>
|
139
|
+
{:else}
|
140
|
+
<div class="icon">
|
141
|
+
<Circle />
|
142
|
+
</div>
|
143
|
+
{/if}
|
144
|
+
{:else}
|
145
|
+
<div class="icon">
|
146
|
+
<Camera />
|
147
|
+
</div>
|
148
|
+
{/if}
|
149
|
+
</button>
|
150
|
+
{/if}
|
151
|
+
</div>
|
152
|
+
|
153
|
+
<style>
|
154
|
+
.wrap {
|
155
|
+
position: relative;
|
156
|
+
width: var(--size-full);
|
157
|
+
height: var(--size-full);
|
158
|
+
min-height: var(--size-60);
|
159
|
+
}
|
160
|
+
|
161
|
+
video {
|
162
|
+
width: var(--size-full);
|
163
|
+
height: var(--size-full);
|
164
|
+
}
|
165
|
+
|
166
|
+
button {
|
167
|
+
display: flex;
|
168
|
+
position: absolute;
|
169
|
+
right: 0px;
|
170
|
+
bottom: var(--size-2);
|
171
|
+
left: 0px;
|
172
|
+
justify-content: center;
|
173
|
+
align-items: center;
|
174
|
+
margin: auto;
|
175
|
+
box-shadow: var(--shadow-drop-lg);
|
176
|
+
border-radius: var(--radius-xl);
|
177
|
+
background-color: rgba(0, 0, 0, 0.9);
|
178
|
+
width: var(--size-10);
|
179
|
+
height: var(--size-10);
|
180
|
+
}
|
181
|
+
|
182
|
+
@media (--screen-md) {
|
183
|
+
button {
|
184
|
+
bottom: var(--size-4);
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
@media (--screen-xl) {
|
189
|
+
button {
|
190
|
+
bottom: var(--size-8);
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
.icon {
|
195
|
+
opacity: 0.8;
|
196
|
+
width: 50%;
|
197
|
+
height: 50%;
|
198
|
+
color: white;
|
199
|
+
}
|
200
|
+
|
201
|
+
.flip {
|
202
|
+
transform: scaleX(-1);
|
203
|
+
}
|
204
|
+
</style>
|
package/package.json
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"name": "@gradio/image",
|
3
|
+
"version": "0.3.0-beta.5",
|
4
|
+
"description": "Gradio UI packages",
|
5
|
+
"type": "module",
|
6
|
+
"main": "./index.svelte",
|
7
|
+
"author": "",
|
8
|
+
"license": "ISC",
|
9
|
+
"private": false,
|
10
|
+
"dependencies": {
|
11
|
+
"cropperjs": "^1.5.12",
|
12
|
+
"lazy-brush": "^1.0.1",
|
13
|
+
"resize-observer-polyfill": "^1.5.1",
|
14
|
+
"@gradio/atoms": "^0.2.0-beta.3",
|
15
|
+
"@gradio/icons": "^0.2.0-beta.0",
|
16
|
+
"@gradio/statustracker": "^0.3.0-beta.5",
|
17
|
+
"@gradio/upload": "^0.3.0-beta.3",
|
18
|
+
"@gradio/utils": "^0.2.0-beta.3"
|
19
|
+
},
|
20
|
+
"main_changeset": true,
|
21
|
+
"exports": {
|
22
|
+
"./package.json": "./package.json",
|
23
|
+
"./interactive": "./interactive/index.ts",
|
24
|
+
"./static": "./static/index.ts",
|
25
|
+
"./example": "./example/index.ts"
|
26
|
+
}
|
27
|
+
}
|
package/shared/utils.ts
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
export const get_coordinates_of_clicked_image = (
|
2
|
+
evt: MouseEvent
|
3
|
+
): [number, number] | null => {
|
4
|
+
let image = evt.currentTarget as HTMLImageElement;
|
5
|
+
|
6
|
+
const imageRect = image.getBoundingClientRect();
|
7
|
+
const xScale = image.naturalWidth / imageRect.width;
|
8
|
+
const yScale = image.naturalHeight / imageRect.height;
|
9
|
+
if (xScale > yScale) {
|
10
|
+
const displayed_height = image.naturalHeight / xScale;
|
11
|
+
const y_offset = (imageRect.height - displayed_height) / 2;
|
12
|
+
var x = Math.round((evt.clientX - imageRect.left) * xScale);
|
13
|
+
var y = Math.round((evt.clientY - imageRect.top - y_offset) * xScale);
|
14
|
+
} else {
|
15
|
+
const displayed_width = image.naturalWidth / yScale;
|
16
|
+
const x_offset = (imageRect.width - displayed_width) / 2;
|
17
|
+
var x = Math.round((evt.clientX - imageRect.left - x_offset) * yScale);
|
18
|
+
var y = Math.round((evt.clientY - imageRect.top) * yScale);
|
19
|
+
}
|
20
|
+
if (x < 0 || x >= image.naturalWidth || y < 0 || y >= image.naturalHeight) {
|
21
|
+
return null;
|
22
|
+
}
|
23
|
+
return [x, y];
|
24
|
+
};
|
package/src/Image.svelte
ADDED
File without changes
|
@@ -0,0 +1,95 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import { createEventDispatcher } from "svelte";
|
3
|
+
import type { SelectData } from "@gradio/utils";
|
4
|
+
import { uploadToHuggingFace } from "@gradio/utils";
|
5
|
+
import { BlockLabel, Empty, IconButton, ShareButton } from "@gradio/atoms";
|
6
|
+
import { Download } from "@gradio/icons";
|
7
|
+
import { get_coordinates_of_clicked_image } from "../shared/utils";
|
8
|
+
|
9
|
+
import { Image } from "@gradio/icons";
|
10
|
+
import { type FileData, normalise_file } from "@gradio/upload";
|
11
|
+
import type { I18nFormatter } from "@gradio/utils";
|
12
|
+
|
13
|
+
export let value: null | FileData;
|
14
|
+
let value_: null | FileData;
|
15
|
+
export let label: string | undefined = undefined;
|
16
|
+
export let show_label: boolean;
|
17
|
+
export let show_download_button = true;
|
18
|
+
export let selectable = false;
|
19
|
+
export let show_share_button = false;
|
20
|
+
export let root: string;
|
21
|
+
export let i18n: I18nFormatter;
|
22
|
+
|
23
|
+
const dispatch = createEventDispatcher<{
|
24
|
+
change: string;
|
25
|
+
select: SelectData;
|
26
|
+
}>();
|
27
|
+
|
28
|
+
$: value && dispatch("change", value.data);
|
29
|
+
|
30
|
+
$: if (value !== value_) {
|
31
|
+
value_ = value;
|
32
|
+
normalise_file(value_, root, null);
|
33
|
+
}
|
34
|
+
|
35
|
+
const handle_click = (evt: MouseEvent): void => {
|
36
|
+
let coordinates = get_coordinates_of_clicked_image(evt);
|
37
|
+
if (coordinates) {
|
38
|
+
dispatch("select", { index: coordinates, value: null });
|
39
|
+
}
|
40
|
+
};
|
41
|
+
</script>
|
42
|
+
|
43
|
+
<BlockLabel {show_label} Icon={Image} label={label || i18n("image.image")} />
|
44
|
+
{#if value_ === null}
|
45
|
+
<Empty unpadded_box={true} size="large"><Image /></Empty>
|
46
|
+
{:else}
|
47
|
+
<div class="icon-buttons">
|
48
|
+
{#if show_download_button}
|
49
|
+
<a
|
50
|
+
href={value_.data}
|
51
|
+
target={window.__is_colab__ ? "_blank" : null}
|
52
|
+
download={"image"}
|
53
|
+
>
|
54
|
+
<IconButton Icon={Download} label={i18n("common.download")} />
|
55
|
+
</a>
|
56
|
+
{/if}
|
57
|
+
{#if show_share_button}
|
58
|
+
<ShareButton
|
59
|
+
{i18n}
|
60
|
+
on:share
|
61
|
+
on:error
|
62
|
+
formatter={async (value) => {
|
63
|
+
if (!value) return "";
|
64
|
+
let url = await uploadToHuggingFace(value, "base64");
|
65
|
+
return `<img src="${url}" />`;
|
66
|
+
}}
|
67
|
+
{value}
|
68
|
+
/>
|
69
|
+
{/if}
|
70
|
+
</div>
|
71
|
+
<!-- TODO: fix -->
|
72
|
+
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
73
|
+
<!-- svelte-ignore a11y-no-noninteractive-element-interactions-->
|
74
|
+
<img src={value_.data} alt="" class:selectable on:click={handle_click} loading="lazy"/>
|
75
|
+
{/if}
|
76
|
+
|
77
|
+
<style>
|
78
|
+
img {
|
79
|
+
width: var(--size-full);
|
80
|
+
height: var(--size-full);
|
81
|
+
object-fit: contain;
|
82
|
+
}
|
83
|
+
|
84
|
+
.selectable {
|
85
|
+
cursor: crosshair;
|
86
|
+
}
|
87
|
+
|
88
|
+
.icon-buttons {
|
89
|
+
display: flex;
|
90
|
+
position: absolute;
|
91
|
+
top: 6px;
|
92
|
+
right: 6px;
|
93
|
+
gap: var(--size-1);
|
94
|
+
}
|
95
|
+
</style>
|
@@ -0,0 +1,76 @@
|
|
1
|
+
<svelte:options accessors={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import type { Gradio, SelectData } from "@gradio/utils";
|
5
|
+
import StaticImage from "./ImagePreview.svelte";
|
6
|
+
|
7
|
+
import { Block } from "@gradio/atoms";
|
8
|
+
|
9
|
+
import { StatusTracker } from "@gradio/statustracker";
|
10
|
+
import type { FileData } from "js/upload/src";
|
11
|
+
import type { LoadingStatus } from "@gradio/statustracker";
|
12
|
+
|
13
|
+
export let elem_id = "";
|
14
|
+
export let elem_classes: string[] = [];
|
15
|
+
export let visible = true;
|
16
|
+
export let value: null | FileData = null;
|
17
|
+
export let label: string;
|
18
|
+
export let show_label: boolean;
|
19
|
+
export let show_download_button: boolean;
|
20
|
+
export let root: string;
|
21
|
+
|
22
|
+
export let height: number | undefined;
|
23
|
+
export let width: number | undefined;
|
24
|
+
|
25
|
+
export let selectable = false;
|
26
|
+
export let container = true;
|
27
|
+
export let scale: number | null = null;
|
28
|
+
export let min_width: number | undefined = undefined;
|
29
|
+
export let loading_status: LoadingStatus;
|
30
|
+
export let show_share_button = false;
|
31
|
+
export let gradio: Gradio<{
|
32
|
+
change: never;
|
33
|
+
error: string;
|
34
|
+
select: SelectData;
|
35
|
+
share: ShareData;
|
36
|
+
}>;
|
37
|
+
|
38
|
+
$: value, gradio.dispatch("change");
|
39
|
+
let dragging: boolean;
|
40
|
+
|
41
|
+
$: value = !value ? null : value;
|
42
|
+
</script>
|
43
|
+
|
44
|
+
<Block
|
45
|
+
{visible}
|
46
|
+
variant={"solid"}
|
47
|
+
border_mode={dragging ? "focus" : "base"}
|
48
|
+
padding={false}
|
49
|
+
{elem_id}
|
50
|
+
{elem_classes}
|
51
|
+
height={height || undefined}
|
52
|
+
{width}
|
53
|
+
allow_overflow={false}
|
54
|
+
{container}
|
55
|
+
{scale}
|
56
|
+
{min_width}
|
57
|
+
>
|
58
|
+
<StatusTracker
|
59
|
+
autoscroll={gradio.autoscroll}
|
60
|
+
i18n={gradio.i18n}
|
61
|
+
{...loading_status}
|
62
|
+
/>
|
63
|
+
<StaticImage
|
64
|
+
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
65
|
+
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
66
|
+
on:error={({ detail }) => gradio.dispatch("error", detail)}
|
67
|
+
{root}
|
68
|
+
{value}
|
69
|
+
{label}
|
70
|
+
{show_label}
|
71
|
+
{show_download_button}
|
72
|
+
{selectable}
|
73
|
+
{show_share_button}
|
74
|
+
i18n={gradio.i18n}
|
75
|
+
/>
|
76
|
+
</Block>
|
package/static/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "./StaticImage.svelte";
|