@gradio/image 0.26.3 → 0.28.0

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.
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, onDestroy, onMount } from "svelte";
2
+ import { onDestroy, onMount } from "svelte";
3
3
  import {
4
4
  Camera,
5
5
  Circle,
@@ -20,33 +20,51 @@
20
20
  import type { Base64File } from "./types";
21
21
 
22
22
  let video_source: HTMLVideoElement;
23
- let available_video_devices: MediaDeviceInfo[] = [];
24
- let selected_device: MediaDeviceInfo | null = null;
25
-
26
- export let stream_state: "open" | "waiting" | "closed" = "closed";
23
+ let available_video_devices = $state<MediaDeviceInfo[]>([]);
24
+ let selected_device = $state<MediaDeviceInfo | null>(null);
27
25
 
28
26
  let canvas: HTMLCanvasElement;
29
- export let streaming = false;
30
- export let pending = false;
31
- export let root = "";
32
- export let stream_every = 1;
33
-
34
- export let mode: "image" | "video" = "image";
35
- export let mirror_webcam: boolean;
36
- export let include_audio: boolean;
37
- export let webcam_constraints: { [key: string]: any } | null = null;
38
- export let i18n: I18nFormatter;
39
- export let upload: Client["upload"];
40
- export let value: FileData | null | Base64File = null;
41
- export let time_limit: number | null = null;
42
- const dispatch = createEventDispatcher<{
43
- stream: Blob | string;
44
- capture: FileData | Blob | null;
45
- error: string;
46
- start_recording: undefined;
47
- stop_recording: undefined;
48
- close_stream: undefined;
49
- }>();
27
+ let {
28
+ stream_state = "closed",
29
+ streaming = false,
30
+ pending = false,
31
+ root = "",
32
+ stream_every = 1,
33
+ mode = "image",
34
+ mirror_webcam,
35
+ include_audio,
36
+ webcam_constraints = null,
37
+ i18n,
38
+ upload,
39
+ value = null,
40
+ time_limit = null,
41
+ onstream,
42
+ oncapture,
43
+ onerror,
44
+ onstart_recording,
45
+ onstop_recording,
46
+ onclose_stream
47
+ }: {
48
+ stream_state?: "open" | "waiting" | "closed";
49
+ streaming?: boolean;
50
+ pending?: boolean;
51
+ root?: string;
52
+ stream_every?: number;
53
+ mode?: "image" | "video";
54
+ mirror_webcam: boolean;
55
+ include_audio: boolean;
56
+ webcam_constraints?: { [key: string]: any } | null;
57
+ i18n: I18nFormatter;
58
+ upload: Client["upload"];
59
+ value?: FileData | null | Base64File;
60
+ time_limit?: number | null;
61
+ onstream?: (value: Blob | string) => void;
62
+ oncapture?: (value: FileData | Blob | null) => void;
63
+ onerror?: (error: string) => void;
64
+ onstart_recording?: () => void;
65
+ onstop_recording?: () => void;
66
+ onclose_stream?: () => void;
67
+ } = $props();
50
68
 
51
69
  onMount(() => {
52
70
  canvas = document.createElement("canvas");
@@ -101,11 +119,11 @@
101
119
  });
102
120
 
103
121
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
104
- dispatch("error", i18n("image.no_webcam_support"));
122
+ onerror?.(i18n("image.no_webcam_support"));
105
123
  }
106
124
  } catch (err) {
107
125
  if (err instanceof DOMException && err.name == "NotAllowedError") {
108
- dispatch("error", i18n("image.allow_webcam_access"));
126
+ onerror?.(i18n("image.allow_webcam_access"));
109
127
  } else {
110
128
  throw err;
111
129
  }
@@ -139,13 +157,17 @@
139
157
  }
140
158
  if (streaming) {
141
159
  const image_data = canvas.toDataURL("image/jpeg");
142
- dispatch("stream", image_data);
160
+ onstream?.(image_data);
143
161
  return;
144
162
  }
145
163
 
146
164
  canvas.toBlob(
147
165
  (blob) => {
148
- dispatch(streaming ? "stream" : "capture", blob);
166
+ if (streaming) {
167
+ onstream?.(blob as Blob);
168
+ } else {
169
+ oncapture?.(blob);
170
+ }
149
171
  },
150
172
  `image/${streaming ? "jpeg" : "png"}`,
151
173
  0.8
@@ -153,7 +175,7 @@
153
175
  }
154
176
  }
155
177
 
156
- let recording = false;
178
+ let recording = $state(false);
157
179
  let recorded_blobs: BlobPart[] = [];
158
180
  let stream: MediaStream;
159
181
  let mimeType: string;
@@ -174,13 +196,13 @@
174
196
  let val_ = (
175
197
  (await upload(val, root))?.filter(Boolean) as FileData[]
176
198
  )[0];
177
- dispatch("capture", val_);
178
- dispatch("stop_recording");
199
+ oncapture?.(val_);
200
+ onstop_recording?.();
179
201
  }
180
202
  };
181
203
  ReaderObj.readAsDataURL(video_blob);
182
204
  } else if (typeof MediaRecorder !== "undefined") {
183
- dispatch("start_recording");
205
+ onstart_recording?.();
184
206
  recorded_blobs = [];
185
207
  let validMimeTypes = ["video/webm", "video/mp4"];
186
208
  for (let validMimeType of validMimeTypes) {
@@ -204,7 +226,7 @@
204
226
  recording = !recording;
205
227
  }
206
228
 
207
- let webcam_accessed = false;
229
+ let webcam_accessed = $state(false);
208
230
 
209
231
  function record_video_or_photo({
210
232
  destroy
@@ -222,11 +244,11 @@
222
244
  }
223
245
 
224
246
  if (!recording && stream) {
225
- dispatch("close_stream");
247
+ onclose_stream?.();
226
248
  }
227
249
  }
228
250
 
229
- let options_open = false;
251
+ let options_open = $state(false);
230
252
 
231
253
  export function click_outside(node: Node, cb: any): any {
232
254
  const handle_click = (event: MouseEvent): void => {
@@ -281,12 +303,12 @@
281
303
  title="grant webcam access"
282
304
  style="height: 100%"
283
305
  >
284
- <WebcamPermissions on:click={async () => access_webcam()} />
306
+ <WebcamPermissions onclick={async () => access_webcam()} />
285
307
  </div>
286
308
  {:else}
287
309
  <div class="button-wrap">
288
310
  <button
289
- on:click={() => record_video_or_photo()}
311
+ onclick={() => record_video_or_photo()}
290
312
  aria-label={mode === "image" ? "capture photo" : "start recording"}
291
313
  >
292
314
  {#if mode === "video" || streaming}
@@ -321,7 +343,7 @@
321
343
  {#if !recording}
322
344
  <button
323
345
  class="icon"
324
- on:click={() => (options_open = true)}
346
+ onclick={() => (options_open = true)}
325
347
  aria-label="select input source"
326
348
  >
327
349
  <DropdownArrow />
@@ -333,14 +355,8 @@
333
355
  class="select-wrap"
334
356
  aria-label="select source"
335
357
  use:click_outside={handle_click_outside}
336
- on:change={handle_device_change}
358
+ onchange={handle_device_change}
337
359
  >
338
- <!-- <button
339
- class="inset-icon"
340
- on:click|stopPropagation={() => (options_open = false)}
341
- >
342
- <DropdownArrow />
343
- </button> -->
344
360
  {#if available_video_devices.length === 0}
345
361
  <option value="">{i18n("common.no_devices")}</option>
346
362
  {:else}
@@ -1,20 +1,7 @@
1
1
  import type { I18nFormatter } from "@gradio/utils";
2
2
  import { type FileData, type Client } from "@gradio/client";
3
3
  import type { Base64File } from "./types";
4
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
- $$bindings?: Bindings;
7
- } & Exports;
8
- (internal: unknown, props: Props & {
9
- $$events?: Events;
10
- $$slots?: Slots;
11
- }): Exports & {
12
- $set?: any;
13
- $on?: any;
14
- };
15
- z_$$bindings?: Bindings;
16
- }
17
- declare const Webcam: $$__sveltets_2_IsomorphicComponent<{
4
+ type $$ComponentProps = {
18
5
  stream_state?: "open" | "waiting" | "closed";
19
6
  streaming?: boolean;
20
7
  pending?: boolean;
@@ -30,18 +17,15 @@ declare const Webcam: $$__sveltets_2_IsomorphicComponent<{
30
17
  upload: Client["upload"];
31
18
  value?: FileData | null | Base64File;
32
19
  time_limit?: number | null;
33
- click_outside?: (node: Node, cb: any) => any;
34
- }, {
35
- stream: CustomEvent<string | Blob>;
36
- capture: CustomEvent<Blob | FileData | null>;
37
- error: CustomEvent<string>;
38
- start_recording: CustomEvent<undefined>;
39
- stop_recording: CustomEvent<undefined>;
40
- close_stream: CustomEvent<undefined>;
41
- } & {
42
- [evt: string]: CustomEvent<any>;
43
- }, {}, {
20
+ onstream?: (value: Blob | string) => void;
21
+ oncapture?: (value: FileData | Blob | null) => void;
22
+ onerror?: (error: string) => void;
23
+ onstart_recording?: () => void;
24
+ onstop_recording?: () => void;
25
+ onclose_stream?: () => void;
26
+ };
27
+ declare const Webcam: import("svelte").Component<$$ComponentProps, {
44
28
  click_outside: (node: Node, cb: any) => any;
45
- }, string>;
46
- type Webcam = InstanceType<typeof Webcam>;
29
+ }, "">;
30
+ type Webcam = ReturnType<typeof Webcam>;
47
31
  export default Webcam;
@@ -1,13 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { Webcam } from "@gradio/icons";
3
- import { createEventDispatcher } from "svelte";
4
3
 
5
- const dispatch = createEventDispatcher<{
6
- click: undefined;
7
- }>();
4
+ let { onclick }: { onclick?: () => void } = $props();
8
5
  </script>
9
6
 
10
- <button style:height="100%" on:click={() => dispatch("click")}>
7
+ <button style:height="100%" {onclick}>
11
8
  <div class="wrap">
12
9
  <span class="icon-wrap">
13
10
  <Webcam />
@@ -1,20 +1,6 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
- $$bindings?: Bindings;
4
- } & Exports;
5
- (internal: unknown, props: {
6
- $$events?: Events;
7
- $$slots?: Slots;
8
- }): Exports & {
9
- $set?: any;
10
- $on?: any;
11
- };
12
- z_$$bindings?: Bindings;
13
- }
14
- declare const WebcamPermissions: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- click: CustomEvent<undefined>;
16
- } & {
17
- [evt: string]: CustomEvent<any>;
18
- }, {}, {}, string>;
19
- type WebcamPermissions = InstanceType<typeof WebcamPermissions>;
1
+ type $$ComponentProps = {
2
+ onclick?: () => void;
3
+ };
4
+ declare const WebcamPermissions: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type WebcamPermissions = ReturnType<typeof WebcamPermissions>;
20
6
  export default WebcamPermissions;
@@ -1,4 +1,4 @@
1
- import type { LoadingStatus } from "@gradio/statustracker";
1
+ import type { ILoadingStatus as LoadingStatus } from "@gradio/statustracker";
2
2
  import type { FileData } from "@gradio/client";
3
3
  import type { CustomButton } from "@gradio/utils";
4
4
  export interface Base64File {
@@ -7,20 +7,13 @@ export const get_coordinates_of_clicked_image = (evt) => {
7
7
  return [NaN, NaN];
8
8
  }
9
9
  const imageRect = image.getBoundingClientRect();
10
- const xScale = image.naturalWidth / imageRect.width;
11
- const yScale = image.naturalHeight / imageRect.height;
12
- if (xScale > yScale) {
13
- const displayed_height = image.naturalHeight / xScale;
14
- const y_offset = (imageRect.height - displayed_height) / 2;
15
- var x = Math.round((evt.clientX - imageRect.left) * xScale);
16
- var y = Math.round((evt.clientY - imageRect.top - y_offset) * xScale);
17
- }
18
- else {
19
- const displayed_width = image.naturalWidth / yScale;
20
- const x_offset = (imageRect.width - displayed_width) / 2;
21
- var x = Math.round((evt.clientX - imageRect.left - x_offset) * yScale);
22
- var y = Math.round((evt.clientY - imageRect.top) * yScale);
23
- }
10
+ // The image is rendered with `object-fit: scale-down`: centered, never
11
+ // scaled above its natural size, so empty space can appear on both axes.
12
+ const scale = Math.min(imageRect.width / image.naturalWidth, imageRect.height / image.naturalHeight, 1);
13
+ const x_offset = (imageRect.width - image.naturalWidth * scale) / 2;
14
+ const y_offset = (imageRect.height - image.naturalHeight * scale) / 2;
15
+ const x = Math.round((evt.clientX - imageRect.left - x_offset) / scale);
16
+ const y = Math.round((evt.clientY - imageRect.top - y_offset) / scale);
24
17
  if (x < 0 || x >= image.naturalWidth || y < 0 || y >= image.naturalHeight) {
25
18
  return null;
26
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/image",
3
- "version": "0.26.3",
3
+ "version": "0.28.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -10,12 +10,12 @@
10
10
  "cropperjs": "^2.0.1",
11
11
  "lazy-brush": "^2.0.2",
12
12
  "resize-observer-polyfill": "^1.5.1",
13
- "@gradio/atoms": "^0.24.0",
14
- "@gradio/icons": "^0.15.1",
15
- "@gradio/statustracker": "^0.14.1",
16
- "@gradio/upload": "^0.17.9",
17
- "@gradio/utils": "^0.12.2",
18
- "@gradio/client": "^2.2.0"
13
+ "@gradio/atoms": "^0.26.0",
14
+ "@gradio/statustracker": "^0.15.1",
15
+ "@gradio/client": "^2.3.1",
16
+ "@gradio/icons": "^0.16.0",
17
+ "@gradio/upload": "^0.18.1",
18
+ "@gradio/utils": "^0.13.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@gradio/preview": "^0.16.2"
@@ -1,24 +1,44 @@
1
1
  <script lang="ts">
2
+ import type { HTMLImgAttributes } from "svelte/elements";
3
+
2
4
  let {
3
- src,
4
- restProps,
5
+ src = "",
6
+ restProps = {},
5
7
  data_testid,
6
- class_names
8
+ class_names = [],
9
+ onload,
10
+ ...imgProps
7
11
  }: {
8
- src: string;
9
- restProps: object;
10
- data_testid: string;
11
- class_names: string[];
12
+ src?: string;
13
+ restProps?: Record<string, any>;
14
+ data_testid?: string;
15
+ class_names?: string[];
16
+ onload?: HTMLImgAttributes["onload"];
17
+ [key: string]: any;
12
18
  } = $props();
19
+
20
+ const without_class = ({
21
+ class: _class,
22
+ ...props
23
+ }: Record<string, any>): Record<string, any> => props;
24
+
25
+ let rest_img_props = $derived(without_class(restProps));
26
+ let direct_img_props = $derived(without_class(imgProps));
27
+ let classes = $derived(
28
+ [class_names.join(" "), restProps.class, imgProps.class]
29
+ .filter(Boolean)
30
+ .join(" ")
31
+ );
13
32
  </script>
14
33
 
15
34
  <!-- svelte-ignore a11y-missing-attribute -->
16
35
  <img
17
36
  {src}
18
- class={(class_names || []).join(" ")}
37
+ class={classes}
19
38
  data-testid={data_testid}
20
- {...restProps}
21
- on:load
39
+ {...rest_img_props}
40
+ {...direct_img_props}
41
+ {onload}
22
42
  />
23
43
 
24
44
  <style>
@@ -1,5 +1,4 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, onMount } from "svelte";
3
2
  import type { SelectData } from "@gradio/utils";
4
3
  import { uploadToHuggingFace } from "@gradio/utils";
5
4
  import {
@@ -19,27 +18,44 @@
19
18
  import type { I18nFormatter } from "@gradio/utils";
20
19
  import type { FileData } from "@gradio/client";
21
20
 
22
- export let value: null | FileData;
23
- export let label: string | undefined = undefined;
24
- export let show_label: boolean;
25
- export let buttons: (string | CustomButtonType)[] = [];
26
- export let on_custom_button_click: ((id: number) => void) | null = null;
27
- export let selectable = false;
28
- export let i18n: I18nFormatter;
29
- export let display_icon_button_wrapper_top_corner = false;
30
- export let fullscreen = false;
31
- export let show_button_background = true;
32
-
33
- const dispatch = createEventDispatcher<{
34
- change: string;
35
- select: SelectData;
36
- fullscreen: boolean;
37
- }>();
21
+ let {
22
+ value,
23
+ label = undefined,
24
+ show_label,
25
+ buttons = [],
26
+ on_custom_button_click = null,
27
+ selectable = false,
28
+ i18n,
29
+ display_icon_button_wrapper_top_corner = false,
30
+ fullscreen = $bindable(false),
31
+ show_button_background = true,
32
+ onselect,
33
+ onfullscreen,
34
+ onshare,
35
+ onerror,
36
+ onload
37
+ }: {
38
+ value: null | FileData;
39
+ label?: string;
40
+ show_label: boolean;
41
+ buttons?: (string | CustomButtonType)[];
42
+ on_custom_button_click?: ((id: number) => void) | null;
43
+ selectable?: boolean;
44
+ i18n: I18nFormatter;
45
+ display_icon_button_wrapper_top_corner?: boolean;
46
+ fullscreen?: boolean;
47
+ show_button_background?: boolean;
48
+ onselect?: (value: SelectData) => void;
49
+ onfullscreen?: (fullscreen: boolean) => void;
50
+ onshare?: (detail: unknown) => void;
51
+ onerror?: (error: string) => void;
52
+ onload?: () => void;
53
+ } = $props();
38
54
 
39
55
  const handle_click = (evt: MouseEvent): void => {
40
56
  let coordinates = get_coordinates_of_clicked_image(evt);
41
57
  if (coordinates) {
42
- dispatch("select", { index: coordinates, value: null });
58
+ onselect?.({ index: coordinates, value: null });
43
59
  }
44
60
  };
45
61
 
@@ -66,7 +82,7 @@
66
82
  {fullscreen}
67
83
  onclick={(is_fullscreen) => {
68
84
  fullscreen = is_fullscreen;
69
- dispatch("fullscreen", is_fullscreen);
85
+ onfullscreen?.(is_fullscreen);
70
86
  }}
71
87
  />
72
88
  {/if}
@@ -78,8 +94,8 @@
78
94
  {#if buttons.some((btn) => typeof btn === "string" && btn === "share")}
79
95
  <ShareButton
80
96
  {i18n}
81
- on:share
82
- on:error
97
+ onshare={(detail) => onshare?.(detail)}
98
+ onerror={(detail) => onerror?.(detail)}
83
99
  formatter={async (value) => {
84
100
  if (!value) return "";
85
101
  let url = await uploadToHuggingFace(value, "url");
@@ -89,12 +105,12 @@
89
105
  />
90
106
  {/if}
91
107
  </IconButtonWrapper>
92
- <button on:click={handle_click}>
108
+ <button onclick={handle_click}>
93
109
  <div class:selectable class="image-frame">
94
110
  <Image
95
111
  src={value.url}
96
112
  restProps={{ loading: "lazy", alt: "" }}
97
- on:load
113
+ {onload}
98
114
  />
99
115
  </div>
100
116
  </button>