@gradio/imageeditor 0.13.0 → 0.14.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.
- package/CHANGELOG.md +22 -0
- package/ImageEditor.stories.svelte +9 -1
- package/Index.svelte +13 -1
- package/InteractiveImageEditor.svelte +10 -1
- package/README.md +2 -0
- package/dist/Index.svelte +6 -0
- package/dist/Index.svelte.d.ts +9 -1
- package/dist/InteractiveImageEditor.svelte +7 -0
- package/dist/InteractiveImageEditor.svelte.d.ts +5 -1
- package/dist/shared/Controls.svelte +13 -1
- package/dist/shared/Controls.svelte.d.ts +2 -0
- package/dist/shared/ImageEditor.svelte +29 -3
- package/dist/shared/ImageEditor.svelte.d.ts +5 -2
- package/dist/shared/Layers.svelte +29 -2
- package/dist/shared/Layers.svelte.d.ts +2 -0
- package/dist/shared/SecondaryToolbar.svelte +1 -0
- package/dist/shared/SecondaryToolbar.svelte.d.ts +2 -0
- package/dist/shared/brush/brush-textures.d.ts +0 -6
- package/dist/shared/brush/brush-textures.js +0 -19
- package/dist/shared/brush/brush.js +3 -2
- package/dist/shared/core/editor.d.ts +15 -6
- package/dist/shared/core/editor.js +76 -33
- package/dist/shared/image/image.js +2 -4
- package/dist/shared/types.d.ts +4 -0
- package/dist/shared/zoom/zoom.js +1 -1
- package/package.json +8 -8
- package/shared/Controls.svelte +14 -40
- package/shared/ImageEditor.svelte +33 -4
- package/shared/Layers.svelte +36 -3
- package/shared/SecondaryToolbar.svelte +7 -1
- package/shared/brush/brush-textures.ts +0 -23
- package/shared/brush/brush.ts +6 -2
- package/shared/core/editor.ts +102 -44
- package/shared/image/image.ts +2 -4
- package/shared/types.ts +5 -0
- package/shared/zoom/zoom.ts +1 -1
|
@@ -25,6 +25,7 @@ export declare class CommandManager {
|
|
|
25
25
|
export declare class LayerManager {
|
|
26
26
|
private layers;
|
|
27
27
|
private active_layer;
|
|
28
|
+
private active_layer_id;
|
|
28
29
|
private draw_textures;
|
|
29
30
|
layer_store: Writable<{
|
|
30
31
|
active_layer: string;
|
|
@@ -32,6 +33,7 @@ export declare class LayerManager {
|
|
|
32
33
|
name: string;
|
|
33
34
|
id: string;
|
|
34
35
|
user_created: boolean;
|
|
36
|
+
visible: boolean;
|
|
35
37
|
}[];
|
|
36
38
|
}>;
|
|
37
39
|
private background_layer;
|
|
@@ -42,6 +44,7 @@ export declare class LayerManager {
|
|
|
42
44
|
private border_region;
|
|
43
45
|
private layer_options;
|
|
44
46
|
constructor(image_container: Container, app: Application, fixed_canvas: boolean, dark: boolean, border_region: number, layer_options: LayerOptions);
|
|
47
|
+
toggle_layer_visibility(id: string): void;
|
|
45
48
|
create_background_layer(width: number, height: number): Container;
|
|
46
49
|
set_layer_options(layer_options: LayerOptions, width: number, height: number): void;
|
|
47
50
|
/**
|
|
@@ -53,11 +56,13 @@ export declare class LayerManager {
|
|
|
53
56
|
* @returns A Promise that resolves to the background Container when the image is loaded
|
|
54
57
|
*/
|
|
55
58
|
create_background_layer_from_url(url: string, width?: number, height?: number): Promise<Container>;
|
|
56
|
-
create_layer({ width, height, layer_name, user_created }: {
|
|
59
|
+
create_layer({ width, height, layer_name, user_created, layer_id, make_active }: {
|
|
57
60
|
width: number;
|
|
58
61
|
height: number;
|
|
59
62
|
layer_name?: string;
|
|
60
63
|
user_created: boolean;
|
|
64
|
+
layer_id?: string;
|
|
65
|
+
make_active?: boolean;
|
|
61
66
|
}): Container;
|
|
62
67
|
/**
|
|
63
68
|
* Creates a new layer with an image loaded from a URL
|
|
@@ -106,6 +111,7 @@ interface ImageEditorOptions {
|
|
|
106
111
|
border_region?: number;
|
|
107
112
|
layer_options?: LayerOptions;
|
|
108
113
|
pad_bottom?: number;
|
|
114
|
+
theme_mode?: "dark" | "light";
|
|
109
115
|
}
|
|
110
116
|
export interface ImageEditorContext {
|
|
111
117
|
app: Application;
|
|
@@ -132,8 +138,8 @@ export interface ImageEditorContext {
|
|
|
132
138
|
y: number;
|
|
133
139
|
};
|
|
134
140
|
animate?: boolean;
|
|
135
|
-
}) => void
|
|
136
|
-
execute_command: (command: Command) => void;
|
|
141
|
+
}) => Promise<void>;
|
|
142
|
+
execute_command: (command: Command) => Promise<void> | void;
|
|
137
143
|
resize_canvas: (width: number, height: number) => void;
|
|
138
144
|
reset: () => void;
|
|
139
145
|
set_background_image: (image: Sprite) => void;
|
|
@@ -170,6 +176,7 @@ export declare class ImageEditor {
|
|
|
170
176
|
name: string;
|
|
171
177
|
id: string;
|
|
172
178
|
user_created: boolean;
|
|
179
|
+
visible: boolean;
|
|
173
180
|
}[];
|
|
174
181
|
}>;
|
|
175
182
|
private outline_container;
|
|
@@ -184,6 +191,7 @@ export declare class ImageEditor {
|
|
|
184
191
|
private overlay_container;
|
|
185
192
|
private overlay_graphics;
|
|
186
193
|
private pad_bottom;
|
|
194
|
+
private theme_mode;
|
|
187
195
|
constructor(options: ImageEditorOptions);
|
|
188
196
|
private get context();
|
|
189
197
|
init(): Promise<void>;
|
|
@@ -199,8 +207,8 @@ export declare class ImageEditor {
|
|
|
199
207
|
y: number;
|
|
200
208
|
};
|
|
201
209
|
animate?: boolean;
|
|
202
|
-
}): void
|
|
203
|
-
execute_command(command: Command): void
|
|
210
|
+
}): Promise<void>;
|
|
211
|
+
execute_command(command: Command): Promise<void>;
|
|
204
212
|
undo(): void;
|
|
205
213
|
redo(): void;
|
|
206
214
|
add_image({ image, resize }: {
|
|
@@ -215,7 +223,7 @@ export declare class ImageEditor {
|
|
|
215
223
|
set_tool(tool: ToolbarTool): void;
|
|
216
224
|
set_subtool(subtool: Subtool): void;
|
|
217
225
|
set_background_image(image: Sprite): void;
|
|
218
|
-
reset_canvas(): void
|
|
226
|
+
reset_canvas(): Promise<void>;
|
|
219
227
|
add_layer(): void;
|
|
220
228
|
/**
|
|
221
229
|
* Adds a new layer with an image loaded from a URL
|
|
@@ -248,5 +256,6 @@ export declare class ImageEditor {
|
|
|
248
256
|
}>;
|
|
249
257
|
get background_image_sprite(): Sprite | undefined;
|
|
250
258
|
set_layer_options(layer_options: LayerOptions): void;
|
|
259
|
+
toggle_layer_visibility(id: string): void;
|
|
251
260
|
}
|
|
252
261
|
export {};
|
|
@@ -34,6 +34,7 @@ export class CommandManager {
|
|
|
34
34
|
export class LayerManager {
|
|
35
35
|
layers = [];
|
|
36
36
|
active_layer = null;
|
|
37
|
+
active_layer_id = null;
|
|
37
38
|
draw_textures = new Map();
|
|
38
39
|
layer_store = writable({
|
|
39
40
|
active_layer: "",
|
|
@@ -54,6 +55,17 @@ export class LayerManager {
|
|
|
54
55
|
this.border_region = border_region;
|
|
55
56
|
this.layer_options = layer_options;
|
|
56
57
|
}
|
|
58
|
+
toggle_layer_visibility(id) {
|
|
59
|
+
const layer = this.layers.find((l) => l.id === id);
|
|
60
|
+
if (layer) {
|
|
61
|
+
layer.container.visible = !layer.container.visible;
|
|
62
|
+
layer.visible = layer.container.visible;
|
|
63
|
+
this.layer_store.update((state) => ({
|
|
64
|
+
active_layer: state.active_layer,
|
|
65
|
+
layers: this.layers
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
57
69
|
create_background_layer(width, height) {
|
|
58
70
|
if (this.background_layer) {
|
|
59
71
|
this.background_layer.destroy();
|
|
@@ -171,15 +183,16 @@ export class LayerManager {
|
|
|
171
183
|
return layer;
|
|
172
184
|
}
|
|
173
185
|
}
|
|
174
|
-
create_layer({ width, height, layer_name, user_created }) {
|
|
186
|
+
create_layer({ width, height, layer_name, user_created, layer_id = undefined, make_active = false }) {
|
|
175
187
|
const layer = new Container();
|
|
176
|
-
const
|
|
188
|
+
const _layer_id = layer_id || Math.random().toString(36).substring(2, 15);
|
|
177
189
|
const _layer_name = layer_name || `Layer ${this.layers.length + 1}`;
|
|
178
190
|
this.layers.push({
|
|
179
191
|
name: _layer_name,
|
|
180
|
-
id:
|
|
192
|
+
id: _layer_id,
|
|
181
193
|
container: layer,
|
|
182
|
-
user_created
|
|
194
|
+
user_created,
|
|
195
|
+
visible: true
|
|
183
196
|
});
|
|
184
197
|
this.image_container.addChild(layer);
|
|
185
198
|
// Create a single render texture for drawing with transparency
|
|
@@ -205,9 +218,11 @@ export class LayerManager {
|
|
|
205
218
|
// Store texture for drawing
|
|
206
219
|
this.draw_textures.set(layer, draw_texture);
|
|
207
220
|
this.update_layer_order();
|
|
208
|
-
|
|
221
|
+
if (make_active) {
|
|
222
|
+
this.set_active_layer(_layer_id);
|
|
223
|
+
}
|
|
209
224
|
this.layer_store.set({
|
|
210
|
-
active_layer:
|
|
225
|
+
active_layer: this.active_layer_id || "",
|
|
211
226
|
layers: this.layers
|
|
212
227
|
});
|
|
213
228
|
return layer;
|
|
@@ -299,6 +314,7 @@ export class LayerManager {
|
|
|
299
314
|
this.layers.find((l) => l.id === id)?.container ||
|
|
300
315
|
this.layers[0]?.container ||
|
|
301
316
|
null;
|
|
317
|
+
this.active_layer_id = id;
|
|
302
318
|
this.layer_store.set({
|
|
303
319
|
active_layer: id,
|
|
304
320
|
layers: this.layers
|
|
@@ -329,8 +345,9 @@ export class LayerManager {
|
|
|
329
345
|
}
|
|
330
346
|
this.layers[index].container.destroy();
|
|
331
347
|
if (this.active_layer === this.layers[index].container) {
|
|
332
|
-
this.
|
|
333
|
-
|
|
348
|
+
const new_active_layer = this.layers[Math.max(0, index - 1)] || null;
|
|
349
|
+
this.active_layer = new_active_layer?.container || null;
|
|
350
|
+
this.active_layer_id = new_active_layer?.id || null;
|
|
334
351
|
}
|
|
335
352
|
this.layers = this.layers.filter((l) => l.id !== id);
|
|
336
353
|
this.layer_store.update((_layers) => ({
|
|
@@ -599,19 +616,30 @@ export class LayerManager {
|
|
|
599
616
|
}
|
|
600
617
|
reset_layers(width, height, persist = false) {
|
|
601
618
|
const _layers_to_recreate = persist
|
|
602
|
-
? this.layers.map((layer) => layer.name)
|
|
603
|
-
: this.layer_options.layers;
|
|
619
|
+
? this.layers.map((layer) => [layer.name, layer.id])
|
|
620
|
+
: this.layer_options.layers.map((layer) => [layer, undefined]);
|
|
604
621
|
this.layers.forEach((layer) => {
|
|
605
622
|
this.delete_layer(layer.id);
|
|
606
623
|
});
|
|
607
|
-
for (const
|
|
624
|
+
for (const [layer_name, layer_id] of _layers_to_recreate) {
|
|
608
625
|
this.create_layer({
|
|
609
626
|
width,
|
|
610
627
|
height,
|
|
611
|
-
layer_name:
|
|
612
|
-
user_created: this.layer_options.layers.
|
|
628
|
+
layer_name: layer_name,
|
|
629
|
+
user_created: this.layer_options.layers.find((l) => l === layer_name)
|
|
630
|
+
? false
|
|
631
|
+
: true,
|
|
632
|
+
layer_id: layer_id
|
|
613
633
|
});
|
|
614
634
|
}
|
|
635
|
+
if (!persist) {
|
|
636
|
+
this.active_layer = this.layers[0].container;
|
|
637
|
+
this.active_layer_id = this.layers[0].id;
|
|
638
|
+
}
|
|
639
|
+
this.layer_store.update((state) => ({
|
|
640
|
+
active_layer: this.active_layer_id || this.layers[0].id,
|
|
641
|
+
layers: this.layers
|
|
642
|
+
}));
|
|
615
643
|
}
|
|
616
644
|
init_layers(width, height) {
|
|
617
645
|
for (const layer of this.layers) {
|
|
@@ -626,8 +654,9 @@ export class LayerManager {
|
|
|
626
654
|
});
|
|
627
655
|
}
|
|
628
656
|
this.active_layer = this.layers[0].container;
|
|
657
|
+
this.active_layer_id = this.layers[0].id;
|
|
629
658
|
this.layer_store.update((_layers) => ({
|
|
630
|
-
active_layer:
|
|
659
|
+
active_layer: this.layers[0].id,
|
|
631
660
|
layers: this.layers
|
|
632
661
|
}));
|
|
633
662
|
}
|
|
@@ -739,14 +768,15 @@ export class ImageEditor {
|
|
|
739
768
|
overlay_container;
|
|
740
769
|
overlay_graphics;
|
|
741
770
|
pad_bottom;
|
|
771
|
+
theme_mode;
|
|
742
772
|
constructor(options) {
|
|
743
773
|
this.pad_bottom = options.pad_bottom || 0;
|
|
744
774
|
this.dark = options.dark || false;
|
|
775
|
+
this.theme_mode = options.theme_mode || "dark";
|
|
745
776
|
this.target_element = options.target_element;
|
|
746
777
|
this.width = options.width;
|
|
747
778
|
this.height = options.height;
|
|
748
779
|
this.command_manager = new CommandManager();
|
|
749
|
-
// this.layers = writable([]);
|
|
750
780
|
this.ready = new Promise((resolve) => {
|
|
751
781
|
this.ready_resolve = resolve;
|
|
752
782
|
});
|
|
@@ -816,7 +846,7 @@ export class ImageEditor {
|
|
|
816
846
|
width: container_box.width,
|
|
817
847
|
height: container_box.height,
|
|
818
848
|
backgroundAlpha: this.dark ? 0 : 1,
|
|
819
|
-
backgroundColor: this.dark ?
|
|
849
|
+
backgroundColor: this.theme_mode === "dark" ? "#27272a" : "#ffffff",
|
|
820
850
|
resolution: window.devicePixelRatio,
|
|
821
851
|
autoDensity: true,
|
|
822
852
|
antialias: true,
|
|
@@ -980,11 +1010,6 @@ export class ImageEditor {
|
|
|
980
1010
|
this.layer_manager = new LayerManager(this.image_container, this.app, this.fixed_canvas, this.dark, this.border_region, this.layer_options);
|
|
981
1011
|
this.layers = this.layer_manager.layer_store;
|
|
982
1012
|
}
|
|
983
|
-
// private resize_image(width: number, height: number): void {
|
|
984
|
-
// // Implement image resizing logic here
|
|
985
|
-
// this.image_container.width = width;
|
|
986
|
-
// this.image_container.height = height;
|
|
987
|
-
// }
|
|
988
1013
|
resize_canvas(width, height) {
|
|
989
1014
|
if (this.app.renderer) {
|
|
990
1015
|
this.app.renderer.resize(width, height);
|
|
@@ -1006,22 +1031,31 @@ export class ImageEditor {
|
|
|
1006
1031
|
zoom.setup(this.context, this.current_tool, this.current_subtool);
|
|
1007
1032
|
}
|
|
1008
1033
|
}
|
|
1009
|
-
set_image_properties(properties) {
|
|
1034
|
+
async set_image_properties(properties) {
|
|
1010
1035
|
let hard = typeof properties.animate !== "undefined" ? !properties.animate : true;
|
|
1011
1036
|
if (properties.position) {
|
|
1012
|
-
this.position.set(properties.position, { hard });
|
|
1037
|
+
const pos = this.position.set(properties.position, { hard });
|
|
1038
|
+
if (hard) {
|
|
1039
|
+
await pos;
|
|
1040
|
+
}
|
|
1013
1041
|
}
|
|
1014
1042
|
if (properties.scale) {
|
|
1015
|
-
this.scale.set(properties.scale, { hard });
|
|
1043
|
+
const scale = this.scale.set(properties.scale, { hard });
|
|
1044
|
+
if (hard) {
|
|
1045
|
+
await scale;
|
|
1046
|
+
}
|
|
1016
1047
|
}
|
|
1017
1048
|
if (properties.width && properties.height) {
|
|
1018
1049
|
this.width = properties.width;
|
|
1019
1050
|
this.height = properties.height;
|
|
1020
|
-
this.dimensions.set({ width: properties.width, height: properties.height }, { hard });
|
|
1051
|
+
const dimensions = this.dimensions.set({ width: properties.width, height: properties.height }, { hard });
|
|
1052
|
+
if (hard) {
|
|
1053
|
+
await dimensions;
|
|
1054
|
+
}
|
|
1021
1055
|
}
|
|
1022
1056
|
}
|
|
1023
|
-
execute_command(command) {
|
|
1024
|
-
this.command_manager.execute(command);
|
|
1057
|
+
async execute_command(command) {
|
|
1058
|
+
await this.command_manager.execute(command);
|
|
1025
1059
|
}
|
|
1026
1060
|
undo() {
|
|
1027
1061
|
this.command_manager.undo();
|
|
@@ -1032,9 +1066,12 @@ export class ImageEditor {
|
|
|
1032
1066
|
this.notify("change");
|
|
1033
1067
|
}
|
|
1034
1068
|
async add_image({ image, resize = true }) {
|
|
1035
|
-
const
|
|
1036
|
-
await
|
|
1037
|
-
|
|
1069
|
+
const image_tool = this.tools.get("image");
|
|
1070
|
+
await image_tool.add_image({
|
|
1071
|
+
image,
|
|
1072
|
+
fixed_canvas: this.fixed_canvas,
|
|
1073
|
+
border_region: this.border_region
|
|
1074
|
+
});
|
|
1038
1075
|
}
|
|
1039
1076
|
/**
|
|
1040
1077
|
* Adds an image from a URL as the background layer
|
|
@@ -1071,13 +1108,13 @@ export class ImageEditor {
|
|
|
1071
1108
|
set_background_image(image) {
|
|
1072
1109
|
this.background_image = image;
|
|
1073
1110
|
}
|
|
1074
|
-
reset_canvas() {
|
|
1111
|
+
async reset_canvas() {
|
|
1075
1112
|
this.layer_manager.reset_layers(this.width, this.height);
|
|
1076
1113
|
// Clear background image
|
|
1077
1114
|
this.background_image = undefined;
|
|
1078
1115
|
this.background_image_present.set(false);
|
|
1079
1116
|
// Reset position, scale and dimensions to default values
|
|
1080
|
-
this.set_image_properties({
|
|
1117
|
+
await this.set_image_properties({
|
|
1081
1118
|
width: this.width,
|
|
1082
1119
|
height: this.height,
|
|
1083
1120
|
scale: 1,
|
|
@@ -1106,7 +1143,9 @@ export class ImageEditor {
|
|
|
1106
1143
|
width: this.width,
|
|
1107
1144
|
height: this.height,
|
|
1108
1145
|
layer_name: undefined,
|
|
1109
|
-
user_created: true
|
|
1146
|
+
user_created: true,
|
|
1147
|
+
layer_id: undefined,
|
|
1148
|
+
make_active: true
|
|
1110
1149
|
});
|
|
1111
1150
|
this.notify("change");
|
|
1112
1151
|
}
|
|
@@ -1130,6 +1169,7 @@ export class ImageEditor {
|
|
|
1130
1169
|
for await (const url of layer_urls) {
|
|
1131
1170
|
await this.layer_manager.add_layer_from_url(url);
|
|
1132
1171
|
}
|
|
1172
|
+
this.layer_manager.set_active_layer(_layers[0].id);
|
|
1133
1173
|
// Update resize tool if present with border region
|
|
1134
1174
|
const resize_tool = this.tools.get("resize");
|
|
1135
1175
|
if (resize_tool && typeof resize_tool.set_border_region === "function") {
|
|
@@ -1218,4 +1258,7 @@ export class ImageEditor {
|
|
|
1218
1258
|
this.layer_options = layer_options;
|
|
1219
1259
|
this.layer_manager.set_layer_options(layer_options, this.width, this.height);
|
|
1220
1260
|
}
|
|
1261
|
+
toggle_layer_visibility(id) {
|
|
1262
|
+
this.layer_manager.toggle_layer_visibility(id);
|
|
1263
|
+
}
|
|
1221
1264
|
}
|
|
@@ -22,8 +22,7 @@ export class ImageTool {
|
|
|
22
22
|
cleanup() { }
|
|
23
23
|
async add_image({ image, fixed_canvas, border_region = 0 }) {
|
|
24
24
|
const image_command = new AddImageCommand(this.context, image, fixed_canvas, border_region);
|
|
25
|
-
await
|
|
26
|
-
this.context.execute_command(image_command);
|
|
25
|
+
await this.context.execute_command(image_command);
|
|
27
26
|
}
|
|
28
27
|
set_tool(tool, subtool) {
|
|
29
28
|
this.current_tool = tool;
|
|
@@ -93,7 +92,7 @@ export class AddImageCommand {
|
|
|
93
92
|
// First ensure we have run start() to create the sprite and get dimensions
|
|
94
93
|
const [width, height] = await this.start();
|
|
95
94
|
// Update image properties with the original dimensions and center in viewport
|
|
96
|
-
this.context.set_image_properties({
|
|
95
|
+
await this.context.set_image_properties({
|
|
97
96
|
scale: 1, // Start at 1:1 scale
|
|
98
97
|
position: {
|
|
99
98
|
x: this.context.app.screen.width / 2,
|
|
@@ -115,7 +114,6 @@ export class AddImageCommand {
|
|
|
115
114
|
if (this.sprite) {
|
|
116
115
|
this.sprite.destroy();
|
|
117
116
|
}
|
|
118
|
-
// The background layer will be automatically cleaned up when a new image is added
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
/**
|
package/dist/shared/types.d.ts
CHANGED
package/dist/shared/zoom/zoom.js
CHANGED
|
@@ -110,7 +110,7 @@ export class ZoomTool {
|
|
|
110
110
|
// Setup event listeners
|
|
111
111
|
this.setup_event_listeners();
|
|
112
112
|
// Update context with initial values
|
|
113
|
-
this.image_editor_context.set_image_properties({
|
|
113
|
+
await this.image_editor_context.set_image_properties({
|
|
114
114
|
scale: min_zoom,
|
|
115
115
|
position: { x: initial_x, y: initial_y }
|
|
116
116
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/imageeditor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"pixi-filters": "^6.1.0",
|
|
12
12
|
"pixi.js": "^8.7.3",
|
|
13
13
|
"tinycolor2": "^1.6.0",
|
|
14
|
-
"@gradio/
|
|
15
|
-
"@gradio/
|
|
16
|
-
"@gradio/image": "^0.22.
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/
|
|
19
|
-
"@gradio/
|
|
14
|
+
"@gradio/icons": "^0.12.0",
|
|
15
|
+
"@gradio/client": "^1.14.1",
|
|
16
|
+
"@gradio/image": "^0.22.1",
|
|
17
|
+
"@gradio/statustracker": "^0.10.8",
|
|
18
|
+
"@gradio/upload": "^0.16.1",
|
|
19
|
+
"@gradio/atoms": "^0.15.1",
|
|
20
20
|
"@gradio/wasm": "^0.18.1",
|
|
21
|
-
"@gradio/
|
|
21
|
+
"@gradio/utils": "^0.10.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@gradio/preview": "^0.13.0"
|
package/shared/Controls.svelte
CHANGED
|
@@ -1,62 +1,25 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createEventDispatcher } from "svelte";
|
|
3
3
|
import { IconButton, IconButtonWrapper } from "@gradio/atoms";
|
|
4
|
-
import { Check, Trash, ZoomIn, ZoomOut, Pan } from "@gradio/icons";
|
|
4
|
+
import { Check, Trash, ZoomIn, ZoomOut, Pan, Download } from "@gradio/icons";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Can the current image be saved?
|
|
8
|
-
*/
|
|
9
6
|
export let can_save = false;
|
|
10
|
-
/**
|
|
11
|
-
* Is the current image changeable?
|
|
12
|
-
*/
|
|
13
7
|
export let changeable = false;
|
|
14
|
-
/**
|
|
15
|
-
* The current zoom level.
|
|
16
|
-
*/
|
|
17
8
|
export let current_zoom = 1;
|
|
18
|
-
/**
|
|
19
|
-
* The current tool.
|
|
20
|
-
*/
|
|
21
9
|
export let tool: string;
|
|
22
|
-
/**
|
|
23
|
-
* Is the current zoom level minimum?
|
|
24
|
-
*/
|
|
25
10
|
export let min_zoom = true;
|
|
11
|
+
export let enable_download = false;
|
|
26
12
|
|
|
27
13
|
const dispatch = createEventDispatcher<{
|
|
28
|
-
/**
|
|
29
|
-
* Remove the current image.
|
|
30
|
-
*/
|
|
31
14
|
remove_image: void;
|
|
32
|
-
/**
|
|
33
|
-
* Undo the last action.
|
|
34
|
-
*/
|
|
35
15
|
undo: void;
|
|
36
|
-
/**
|
|
37
|
-
* Redo the last action.
|
|
38
|
-
*/
|
|
39
16
|
redo: void;
|
|
40
|
-
/**
|
|
41
|
-
* Save the current image.
|
|
42
|
-
*/
|
|
43
17
|
save: void;
|
|
44
|
-
/**
|
|
45
|
-
* Zoom in.
|
|
46
|
-
*/
|
|
47
18
|
zoom_in: void;
|
|
48
|
-
/**
|
|
49
|
-
* Zoom out.
|
|
50
|
-
*/
|
|
51
19
|
zoom_out: void;
|
|
52
|
-
/**
|
|
53
|
-
* Set the zoom level.
|
|
54
|
-
*/
|
|
55
20
|
set_zoom: number | "fit";
|
|
56
|
-
/**
|
|
57
|
-
* Pan the image.
|
|
58
|
-
*/
|
|
59
21
|
pan: void;
|
|
22
|
+
download: void;
|
|
60
23
|
}>();
|
|
61
24
|
|
|
62
25
|
let show_zoom_popup = false;
|
|
@@ -81,6 +44,17 @@
|
|
|
81
44
|
</script>
|
|
82
45
|
|
|
83
46
|
<IconButtonWrapper>
|
|
47
|
+
{#if enable_download}
|
|
48
|
+
<IconButton
|
|
49
|
+
Icon={Download}
|
|
50
|
+
label="Download"
|
|
51
|
+
on:click={(event) => {
|
|
52
|
+
dispatch("download");
|
|
53
|
+
event.stopPropagation();
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
{/if}
|
|
57
|
+
|
|
84
58
|
<IconButton
|
|
85
59
|
Icon={Pan}
|
|
86
60
|
label="Pan"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import { create_drag } from "@gradio/upload";
|
|
23
23
|
import SecondaryToolbar from "./SecondaryToolbar.svelte";
|
|
24
24
|
import { Check } from "@gradio/icons";
|
|
25
|
-
import type { LayerOptions, Source, Transform } from "./types";
|
|
25
|
+
import type { LayerOptions, Source, Transform, WebcamOptions } from "./types";
|
|
26
26
|
|
|
27
27
|
import { type ImageBlobs } from "./types";
|
|
28
28
|
import Controls from "./Controls.svelte";
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
history: CommandManager["current_history"];
|
|
37
37
|
upload: void;
|
|
38
38
|
input: void;
|
|
39
|
+
download_error: string;
|
|
39
40
|
}>();
|
|
40
41
|
|
|
41
42
|
export const antialias = true;
|
|
@@ -51,7 +52,6 @@
|
|
|
51
52
|
export let eraser_options: Eraser;
|
|
52
53
|
export let fixed_canvas = false;
|
|
53
54
|
export let root: string;
|
|
54
|
-
export let mirror_webcam = true;
|
|
55
55
|
export let i18n: I18nFormatter;
|
|
56
56
|
export let upload: Client["upload"];
|
|
57
57
|
export let composite: FileData | null;
|
|
@@ -60,6 +60,9 @@
|
|
|
60
60
|
export let border_region = 0;
|
|
61
61
|
export let layer_options: LayerOptions;
|
|
62
62
|
export let current_tool: ToolbarTool;
|
|
63
|
+
export let webcam_options: WebcamOptions;
|
|
64
|
+
export let show_download_button = false;
|
|
65
|
+
export let theme_mode: "dark" | "light";
|
|
63
66
|
|
|
64
67
|
let pixi_target: HTMLDivElement;
|
|
65
68
|
let pixi_target_crop: HTMLDivElement;
|
|
@@ -256,7 +259,8 @@
|
|
|
256
259
|
tools: ["image", zoom, new ResizeTool(), brush],
|
|
257
260
|
fixed_canvas,
|
|
258
261
|
border_region,
|
|
259
|
-
layer_options
|
|
262
|
+
layer_options,
|
|
263
|
+
theme_mode
|
|
260
264
|
});
|
|
261
265
|
|
|
262
266
|
brush.on("change", () => {
|
|
@@ -563,6 +567,22 @@
|
|
|
563
567
|
dispatch("change");
|
|
564
568
|
dispatch("input");
|
|
565
569
|
}
|
|
570
|
+
|
|
571
|
+
async function handle_download(): Promise<void> {
|
|
572
|
+
const blobs = await editor.get_blobs();
|
|
573
|
+
|
|
574
|
+
const blob = blobs.composite;
|
|
575
|
+
if (!blob) {
|
|
576
|
+
dispatch("download_error", "Unable to generate image to download.");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const url = URL.createObjectURL(blob);
|
|
580
|
+
const link = document.createElement("a");
|
|
581
|
+
link.href = url;
|
|
582
|
+
link.download = "image.png";
|
|
583
|
+
link.click();
|
|
584
|
+
URL.revokeObjectURL(url);
|
|
585
|
+
}
|
|
566
586
|
</script>
|
|
567
587
|
|
|
568
588
|
<div
|
|
@@ -599,6 +619,8 @@
|
|
|
599
619
|
on:pan={(e) => {
|
|
600
620
|
handle_tool_change({ tool: "pan" });
|
|
601
621
|
}}
|
|
622
|
+
enable_download={show_download_button}
|
|
623
|
+
on:download={() => handle_download()}
|
|
602
624
|
/>
|
|
603
625
|
{/if}
|
|
604
626
|
|
|
@@ -633,11 +655,12 @@
|
|
|
633
655
|
on:capture={handle_capture}
|
|
634
656
|
on:error
|
|
635
657
|
on:drag
|
|
636
|
-
{mirror_webcam}
|
|
637
658
|
streaming={false}
|
|
638
659
|
mode="image"
|
|
639
660
|
include_audio={false}
|
|
640
661
|
{i18n}
|
|
662
|
+
mirror_webcam={webcam_options.mirror}
|
|
663
|
+
webcam_constraints={webcam_options.constraints}
|
|
641
664
|
/>
|
|
642
665
|
</div>
|
|
643
666
|
</div>
|
|
@@ -652,6 +675,9 @@
|
|
|
652
675
|
}}
|
|
653
676
|
on:change_layer={(e) => {
|
|
654
677
|
editor.set_layer(e.detail);
|
|
678
|
+
if (current_tool === "draw") {
|
|
679
|
+
handle_tool_change({ tool: "draw" });
|
|
680
|
+
}
|
|
655
681
|
}}
|
|
656
682
|
on:move_layer={(e) => {
|
|
657
683
|
editor.move_layer(e.detail.id, e.detail.direction);
|
|
@@ -659,6 +685,9 @@
|
|
|
659
685
|
on:delete_layer={(e) => {
|
|
660
686
|
editor.delete_layer(e.detail);
|
|
661
687
|
}}
|
|
688
|
+
on:toggle_layer_visibility={(e) => {
|
|
689
|
+
editor.toggle_layer_visibility(e.detail);
|
|
690
|
+
}}
|
|
662
691
|
/>
|
|
663
692
|
{/if}
|
|
664
693
|
{/if}
|
package/shared/Layers.svelte
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { click_outside } from "./utils/events";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Layers,
|
|
5
|
+
Clear,
|
|
6
|
+
ArrowUp,
|
|
7
|
+
ArrowDown,
|
|
8
|
+
Plus,
|
|
9
|
+
Visibility,
|
|
10
|
+
VisibilityOff
|
|
11
|
+
} from "@gradio/icons";
|
|
4
12
|
import { IconButton } from "@gradio/atoms";
|
|
5
13
|
import { createEventDispatcher } from "svelte";
|
|
6
14
|
import type { Writable } from "svelte/store";
|
|
@@ -10,11 +18,17 @@
|
|
|
10
18
|
change_layer: string;
|
|
11
19
|
move_layer: { id: string; direction: "up" | "down" };
|
|
12
20
|
delete_layer: string;
|
|
21
|
+
toggle_layer_visibility: string;
|
|
13
22
|
}>();
|
|
14
23
|
|
|
15
24
|
export let layers: Writable<{
|
|
16
25
|
active_layer: string;
|
|
17
|
-
layers: {
|
|
26
|
+
layers: {
|
|
27
|
+
name: string;
|
|
28
|
+
id: string;
|
|
29
|
+
user_created: boolean;
|
|
30
|
+
visible: boolean;
|
|
31
|
+
}[];
|
|
18
32
|
}>;
|
|
19
33
|
export let enable_additional_layers = true;
|
|
20
34
|
export let enable_layers = true;
|
|
@@ -56,8 +70,27 @@
|
|
|
56
70
|
</button>
|
|
57
71
|
{#if show_layers}
|
|
58
72
|
<ul>
|
|
59
|
-
{#each $layers.layers as { id, name, user_created }, i (i)}
|
|
73
|
+
{#each $layers.layers as { id, name, user_created, visible }, i (i)}
|
|
60
74
|
<li>
|
|
75
|
+
{#if !visible}
|
|
76
|
+
<IconButton
|
|
77
|
+
Icon={VisibilityOff}
|
|
78
|
+
size="small"
|
|
79
|
+
on:click={(e) => {
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
dispatch("toggle_layer_visibility", id);
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
{:else}
|
|
85
|
+
<IconButton
|
|
86
|
+
Icon={Visibility}
|
|
87
|
+
size="small"
|
|
88
|
+
on:click={(e) => {
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
dispatch("toggle_layer_visibility", id);
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
{/if}
|
|
61
94
|
<button
|
|
62
95
|
class:selected_layer={$layers.active_layer === id}
|
|
63
96
|
aria-label={`layer-${i + 1}`}
|