@gradio/imageeditor 0.12.13 → 0.13.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 +23 -0
- package/Example.svelte +1 -1
- package/IMAGE_EDITOR_OVERVIEW.md +224 -0
- package/ImageEditor.stories.svelte +4 -4
- package/Index.svelte +35 -46
- package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +56 -103
- package/dist/Example.svelte.d.ts +1 -1
- package/dist/Index.svelte +29 -33
- package/dist/Index.svelte.d.ts +17 -16
- package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +47 -88
- package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +13 -15
- package/dist/shared/Anchor.svelte +311 -0
- package/dist/shared/Anchor.svelte.d.ts +18 -0
- package/dist/shared/Controls.svelte +162 -23
- package/dist/shared/Controls.svelte.d.ts +7 -2
- package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
- package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
- package/dist/shared/ImageEditor.svelte +546 -289
- package/dist/shared/ImageEditor.svelte.d.ts +51 -56
- package/dist/shared/Layers.svelte +233 -0
- package/dist/shared/Layers.svelte.d.ts +35 -0
- package/dist/shared/Resize.svelte +218 -0
- package/dist/shared/Resize.svelte.d.ts +29 -0
- package/dist/shared/SecondaryToolbar.svelte +54 -0
- package/dist/shared/SecondaryToolbar.svelte.d.ts +35 -0
- package/dist/shared/Toolbar.svelte +337 -0
- package/dist/shared/Toolbar.svelte.d.ts +42 -0
- package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
- package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
- package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
- package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
- package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
- package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
- package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
- package/dist/shared/brush/brush-cursor.d.ts +101 -0
- package/dist/shared/brush/brush-cursor.js +327 -0
- package/dist/shared/brush/brush-textures.d.ts +71 -0
- package/dist/shared/brush/brush-textures.js +404 -0
- package/dist/shared/brush/brush-utils.d.ts +32 -0
- package/dist/shared/brush/brush-utils.js +59 -0
- package/dist/shared/brush/brush.d.ts +152 -0
- package/dist/shared/brush/brush.js +396 -0
- package/dist/shared/brush/types.d.ts +37 -0
- package/dist/shared/brush/types.js +1 -0
- package/dist/shared/core/editor.d.ts +252 -0
- package/dist/shared/core/editor.js +1221 -0
- package/dist/shared/crop/crop.d.ts +201 -0
- package/dist/shared/crop/crop.js +727 -0
- package/dist/shared/image/image.d.ts +89 -0
- package/dist/shared/image/image.js +185 -0
- package/dist/shared/resize/resize.d.ts +270 -0
- package/dist/shared/resize/resize.js +1413 -0
- package/dist/shared/types.d.ts +12 -0
- package/dist/shared/utils/pixi.d.ts +16 -57
- package/dist/shared/utils/pixi.js +15 -113
- package/dist/shared/zoom/zoom.d.ts +136 -0
- package/dist/shared/zoom/zoom.js +548 -0
- package/package.json +11 -10
- package/shared/Anchor.svelte +384 -0
- package/shared/Controls.svelte +194 -27
- package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
- package/shared/ImageEditor.svelte +682 -349
- package/shared/Layers.svelte +249 -0
- package/shared/Resize.svelte +239 -0
- package/shared/SecondaryToolbar.svelte +61 -0
- package/shared/Toolbar.svelte +386 -0
- package/shared/brush/BRUSH_TOOL.md +134 -0
- package/shared/brush/BrushOptions.svelte +200 -0
- package/shared/{tools → brush}/BrushSize.svelte +2 -30
- package/shared/{tools → brush}/ColorField.svelte +3 -1
- package/shared/{tools → brush}/ColorPicker.svelte +86 -9
- package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
- package/shared/brush/brush-cursor.ts +408 -0
- package/shared/brush/brush-textures.ts +508 -0
- package/shared/brush/brush-utils.ts +76 -0
- package/shared/brush/brush.ts +506 -0
- package/shared/brush/types.ts +41 -0
- package/shared/core/EDITOR.md +182 -0
- package/shared/core/editor.ts +1730 -0
- package/shared/crop/CROP.md +139 -0
- package/shared/crop/crop.ts +1027 -0
- package/shared/image/IMAGE.md +135 -0
- package/shared/image/image.ts +300 -0
- package/shared/resize/resize.ts +1894 -0
- package/shared/types.ts +14 -0
- package/shared/utils/pixi.ts +19 -246
- package/shared/zoom/zoom.ts +729 -0
- package/dist/shared/layers/Layers.svelte +0 -231
- package/dist/shared/layers/Layers.svelte.d.ts +0 -18
- package/dist/shared/layers/utils.d.ts +0 -73
- package/dist/shared/layers/utils.js +0 -156
- package/dist/shared/tools/Brush.svelte +0 -215
- package/dist/shared/tools/Brush.svelte.d.ts +0 -29
- package/dist/shared/tools/Crop.svelte +0 -155
- package/dist/shared/tools/Crop.svelte.d.ts +0 -16
- package/dist/shared/tools/Cropper.svelte +0 -271
- package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
- package/dist/shared/tools/Handle.svelte +0 -260
- package/dist/shared/tools/Handle.svelte.d.ts +0 -29
- package/dist/shared/tools/Sources.svelte +0 -257
- package/dist/shared/tools/Sources.svelte.d.ts +0 -34
- package/dist/shared/tools/Tools.svelte +0 -102
- package/dist/shared/tools/Tools.svelte.d.ts +0 -48
- package/dist/shared/tools/brush.d.ts +0 -62
- package/dist/shared/tools/brush.js +0 -133
- package/dist/shared/tools/crop.d.ts +0 -15
- package/dist/shared/tools/crop.js +0 -154
- package/dist/shared/tools/index.d.ts +0 -5
- package/dist/shared/tools/index.js +0 -5
- package/dist/shared/tools/sources.d.ts +0 -46
- package/dist/shared/tools/sources.js +0 -121
- package/dist/shared/tools/types.d.ts +0 -1
- package/shared/layers/Layers.svelte +0 -250
- package/shared/layers/utils.ts +0 -294
- package/shared/tools/Brush.svelte +0 -280
- package/shared/tools/BrushOptions.svelte +0 -180
- package/shared/tools/Crop.svelte +0 -189
- package/shared/tools/Cropper.svelte +0 -346
- package/shared/tools/Handle.svelte +0 -289
- package/shared/tools/Sources.svelte +0 -283
- package/shared/tools/Tools.svelte +0 -162
- package/shared/tools/brush.ts +0 -247
- package/shared/tools/crop.ts +0 -213
- package/shared/tools/index.ts +0 -5
- package/shared/tools/sources.ts +0 -195
- package/shared/tools/types.ts +0 -1
- /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
- /package/dist/shared/{tools/types.js → types.js} +0 -0
|
@@ -1,351 +1,608 @@
|
|
|
1
|
-
<script context="module">
|
|
2
|
-
import {} from "./utils/commands";
|
|
3
|
-
export const EDITOR_KEY = Symbol("editor");
|
|
4
|
-
import {} from "./tools";
|
|
1
|
+
<script context="module">export const EDITOR_KEY = Symbol("editor");
|
|
5
2
|
</script>
|
|
6
3
|
|
|
7
|
-
<script>import { onMount,
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {} from "
|
|
13
|
-
import {
|
|
4
|
+
<script>import { onMount, createEventDispatcher, tick } from "svelte";
|
|
5
|
+
import Toolbar, {} from "./Toolbar.svelte";
|
|
6
|
+
import { CropTool } from "./crop/crop";
|
|
7
|
+
import { ResizeTool } from "./resize/resize";
|
|
8
|
+
import { Webcam } from "@gradio/image";
|
|
9
|
+
import tinycolor, {} from "tinycolor2";
|
|
10
|
+
import { ZoomTool } from "./zoom/zoom";
|
|
11
|
+
import {} from "./utils/commands";
|
|
12
|
+
import { ImageEditor } from "./core/editor";
|
|
13
|
+
import {} from "./brush/types";
|
|
14
|
+
import { BrushTool } from "./brush/brush";
|
|
15
|
+
import { create_drag } from "@gradio/upload";
|
|
16
|
+
import SecondaryToolbar from "./SecondaryToolbar.svelte";
|
|
17
|
+
import { Check } from "@gradio/icons";
|
|
18
|
+
import {} from "./types";
|
|
14
19
|
import Controls from "./Controls.svelte";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export let changeable = false;
|
|
18
|
-
export let history;
|
|
19
|
-
export let bg = false;
|
|
20
|
-
export let sources;
|
|
20
|
+
import IconButton from "./IconButton.svelte";
|
|
21
|
+
const { drag, open_file_upload } = create_drag();
|
|
21
22
|
const dispatch = createEventDispatcher();
|
|
22
|
-
export
|
|
23
|
+
export const antialias = true;
|
|
24
|
+
export const full_history = null;
|
|
25
|
+
export let changeable = false;
|
|
26
|
+
export let sources = ["upload", "webcam", "clipboard"];
|
|
27
|
+
export let transforms = ["crop", "resize"];
|
|
23
28
|
export let canvas_size;
|
|
24
|
-
export let
|
|
25
|
-
export let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
export let
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
child_left: 0,
|
|
42
|
-
child_right: 0,
|
|
43
|
-
child_bottom: 0
|
|
44
|
-
});
|
|
45
|
-
export let canvas_height = void 0;
|
|
46
|
-
$:
|
|
47
|
-
height = $editor_box.child_height;
|
|
29
|
+
export let is_dragging = false;
|
|
30
|
+
export let background_image = false;
|
|
31
|
+
export let brush_options;
|
|
32
|
+
export let eraser_options;
|
|
33
|
+
export let fixed_canvas = false;
|
|
34
|
+
export let root;
|
|
35
|
+
export let mirror_webcam = true;
|
|
36
|
+
export let i18n;
|
|
37
|
+
export let upload;
|
|
38
|
+
export let composite;
|
|
39
|
+
export let layers;
|
|
40
|
+
export let background;
|
|
41
|
+
export let border_region = 0;
|
|
42
|
+
export let layer_options;
|
|
43
|
+
export let current_tool;
|
|
44
|
+
let pixi_target;
|
|
45
|
+
let pixi_target_crop;
|
|
48
46
|
$:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
stiffness: 0.1,
|
|
55
|
-
damping: 0.5
|
|
47
|
+
if (layer_options) {
|
|
48
|
+
if (check_if_should_init()) {
|
|
49
|
+
editor.set_layer_options(layer_options);
|
|
50
|
+
refresh_tools();
|
|
51
|
+
}
|
|
56
52
|
}
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
function refresh_tools() {
|
|
54
|
+
editor.set_tool(current_tool);
|
|
55
|
+
editor.set_subtool(current_subtool);
|
|
56
|
+
}
|
|
57
|
+
function check_if_should_init() {
|
|
58
|
+
return layer_options && editor && ready;
|
|
59
|
+
}
|
|
60
|
+
export let has_drawn = false;
|
|
61
|
+
export async function get_blobs() {
|
|
62
|
+
if (!editor)
|
|
63
|
+
return { background: null, layers: [], composite: null };
|
|
64
|
+
if (!background_image && !has_drawn && !layers.length)
|
|
65
|
+
return { background: null, layers: [], composite: null };
|
|
66
|
+
const blobs = await editor.get_blobs();
|
|
67
|
+
return blobs;
|
|
68
|
+
}
|
|
69
|
+
let editor;
|
|
70
|
+
export function add_image(image) {
|
|
71
|
+
editor.add_image({ image });
|
|
72
|
+
}
|
|
73
|
+
let pending_bg;
|
|
74
|
+
export async function add_image_from_url(source) {
|
|
75
|
+
if (!editor || !source || !check_if_should_init())
|
|
76
|
+
return;
|
|
77
|
+
let url;
|
|
78
|
+
if (typeof source === "string") {
|
|
79
|
+
url = source;
|
|
80
|
+
} else if (source?.meta?._type === "gradio.FileData" && source?.url) {
|
|
81
|
+
url = source.url;
|
|
82
|
+
} else {
|
|
83
|
+
console.warn("Invalid source provided to add_image_from_url:", source);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
pending_bg = editor.add_image_from_url(url);
|
|
88
|
+
let pending_crop = crop.add_image_from_url(url);
|
|
89
|
+
await Promise.all([pending_bg, pending_crop]);
|
|
90
|
+
crop.set_tool("image");
|
|
91
|
+
crop.set_subtool("crop");
|
|
92
|
+
background_image = true;
|
|
93
|
+
dispatch("upload");
|
|
94
|
+
dispatch("input");
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error("Error adding image from URL:", error);
|
|
64
97
|
}
|
|
65
|
-
return history2;
|
|
66
98
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
export async function add_layers_from_url(source) {
|
|
100
|
+
if (!editor || !source.length || !check_if_should_init())
|
|
101
|
+
return;
|
|
102
|
+
if (Array.isArray(source) && source.every((item) => item?.meta?._type === "gradio.FileData")) {
|
|
103
|
+
try {
|
|
104
|
+
await pending_bg;
|
|
105
|
+
await editor.add_layers_from_url(source.map((item) => item.url));
|
|
106
|
+
dispatch("change");
|
|
107
|
+
dispatch("input");
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("Error adding layer from URL:", error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let brush;
|
|
114
|
+
let zoom;
|
|
115
|
+
let zoom_level = 1;
|
|
116
|
+
let ready = false;
|
|
117
|
+
let mounted = false;
|
|
118
|
+
let min_zoom = true;
|
|
119
|
+
let last_dimensions = { width: 0, height: 0 };
|
|
120
|
+
async function handle_visibility_change() {
|
|
121
|
+
if (!editor || !ready || !zoom)
|
|
122
|
+
return;
|
|
123
|
+
await tick();
|
|
124
|
+
const is_visible = pixi_target.offsetParent !== null;
|
|
125
|
+
if (is_visible) {
|
|
126
|
+
const current_dimensions = pixi_target.getBoundingClientRect();
|
|
127
|
+
if (current_dimensions.width !== last_dimensions.width || current_dimensions.height !== last_dimensions.height) {
|
|
128
|
+
zoom.set_zoom("fit");
|
|
129
|
+
last_dimensions = {
|
|
130
|
+
width: current_dimensions.width,
|
|
131
|
+
height: current_dimensions.height
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
73
135
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
82
|
-
const sort_order = ["bg", "layers", "crop", "draw", "erase"];
|
|
83
|
-
const editor_context = setContext(EDITOR_KEY, {
|
|
84
|
-
pixi,
|
|
85
|
-
current_layer: writable(null),
|
|
86
|
-
dimensions,
|
|
87
|
-
editor_box,
|
|
88
|
-
toolbar_box,
|
|
89
|
-
active_tool,
|
|
90
|
-
crop,
|
|
91
|
-
position_spring,
|
|
92
|
-
command_manager: CommandManager,
|
|
93
|
-
current_history,
|
|
94
|
-
register_context: (type, {
|
|
95
|
-
reset_fn,
|
|
96
|
-
init_fn
|
|
97
|
-
}) => {
|
|
98
|
-
contexts.update((c) => [...c, type]);
|
|
99
|
-
init_context.update((c) => ({ ...c, [type]: init_fn }));
|
|
100
|
-
reset_context.update((c) => ({ ...c, [type]: reset_fn }));
|
|
101
|
-
},
|
|
102
|
-
reset: (clear_image, dimensions2) => {
|
|
103
|
-
bg = false;
|
|
104
|
-
const _sorted_contexts = $contexts.sort((a, b) => {
|
|
105
|
-
return sort_order.indexOf(a) - sort_order.indexOf(b);
|
|
136
|
+
onMount(() => {
|
|
137
|
+
let intersection_observer;
|
|
138
|
+
let resize_observer;
|
|
139
|
+
init_image_editor().then(() => {
|
|
140
|
+
mounted = true;
|
|
141
|
+
intersection_observer = new IntersectionObserver(() => {
|
|
142
|
+
handle_visibility_change();
|
|
106
143
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
144
|
+
resize_observer = new ResizeObserver(() => {
|
|
145
|
+
handle_visibility_change();
|
|
146
|
+
});
|
|
147
|
+
intersection_observer.observe(pixi_target);
|
|
148
|
+
resize_observer.observe(pixi_target);
|
|
149
|
+
});
|
|
150
|
+
return () => {
|
|
151
|
+
if (intersection_observer) {
|
|
152
|
+
intersection_observer.disconnect();
|
|
111
153
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (k === "bg" && !clear_image) {
|
|
115
|
-
continue;
|
|
116
|
-
} else {
|
|
117
|
-
$init_context[k]?.(dimensions2);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
CommandManager.reset();
|
|
121
|
-
$pixi?.resize?.(...dimensions2);
|
|
154
|
+
if (resize_observer) {
|
|
155
|
+
resize_observer.disconnect();
|
|
122
156
|
}
|
|
123
|
-
|
|
157
|
+
if (editor) {
|
|
158
|
+
editor.destroy();
|
|
159
|
+
}
|
|
160
|
+
};
|
|
124
161
|
});
|
|
125
|
-
let
|
|
126
|
-
let
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
let crop;
|
|
163
|
+
let crop_zoom;
|
|
164
|
+
async function init_image_editor() {
|
|
165
|
+
brush = new BrushTool();
|
|
166
|
+
zoom = new ZoomTool();
|
|
167
|
+
editor = new ImageEditor({
|
|
168
|
+
target_element: pixi_target,
|
|
169
|
+
width: canvas_size[0],
|
|
170
|
+
height: canvas_size[1],
|
|
171
|
+
tools: ["image", zoom, new ResizeTool(), brush],
|
|
172
|
+
fixed_canvas,
|
|
173
|
+
border_region,
|
|
174
|
+
layer_options
|
|
175
|
+
});
|
|
176
|
+
brush.on("change", () => {
|
|
177
|
+
has_drawn = true;
|
|
178
|
+
});
|
|
179
|
+
crop_zoom = new ZoomTool();
|
|
180
|
+
crop = new ImageEditor({
|
|
181
|
+
target_element: pixi_target_crop,
|
|
182
|
+
width: canvas_size[0],
|
|
183
|
+
height: canvas_size[1],
|
|
184
|
+
tools: ["image", crop_zoom, new CropTool()],
|
|
185
|
+
dark: true,
|
|
186
|
+
fixed_canvas: false,
|
|
187
|
+
border_region: 0,
|
|
188
|
+
pad_bottom: 40
|
|
189
|
+
});
|
|
190
|
+
editor.scale.subscribe((_scale) => {
|
|
191
|
+
zoom_level = _scale;
|
|
192
|
+
});
|
|
193
|
+
editor.min_zoom.subscribe((is_min_zoom) => {
|
|
194
|
+
min_zoom = is_min_zoom;
|
|
195
|
+
});
|
|
196
|
+
editor.dimensions.subscribe((dimensions) => {
|
|
197
|
+
last_dimensions = { ...dimensions };
|
|
159
198
|
});
|
|
199
|
+
await Promise.all([editor.ready, crop.ready]).then(() => {
|
|
200
|
+
handle_tool_change({ tool: "image" });
|
|
201
|
+
ready = true;
|
|
202
|
+
if (sources.length > 0) {
|
|
203
|
+
handle_tool_change({ tool: "image" });
|
|
204
|
+
} else {
|
|
205
|
+
handle_tool_change({ tool: "draw" });
|
|
206
|
+
}
|
|
207
|
+
crop.set_subtool("crop");
|
|
208
|
+
});
|
|
209
|
+
editor.on("change", () => {
|
|
210
|
+
dispatch("change");
|
|
211
|
+
});
|
|
212
|
+
if (background || layers.length > 0) {
|
|
213
|
+
if (background) {
|
|
214
|
+
await add_image_from_url(background);
|
|
215
|
+
}
|
|
216
|
+
if (layers.length > 0) {
|
|
217
|
+
await add_layers_from_url(layers);
|
|
218
|
+
}
|
|
219
|
+
handle_tool_change({ tool: "draw" });
|
|
220
|
+
} else if (composite) {
|
|
221
|
+
await add_image_from_url(composite);
|
|
222
|
+
handle_tool_change({ tool: "draw" });
|
|
223
|
+
}
|
|
160
224
|
}
|
|
161
225
|
$:
|
|
162
|
-
if (
|
|
163
|
-
|
|
226
|
+
if (background == null && layers.length == 0 && composite == null && editor && ready) {
|
|
227
|
+
editor.reset_canvas();
|
|
228
|
+
handle_tool_change({ tool: "image" });
|
|
229
|
+
background_image = false;
|
|
230
|
+
has_drawn = false;
|
|
164
231
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
function reposition_canvas() {
|
|
171
|
-
if (!$editor_box)
|
|
232
|
+
$:
|
|
233
|
+
current_tool === "image" && current_subtool === "crop" && crop_zoom.set_zoom("fit");
|
|
234
|
+
async function handle_files(files) {
|
|
235
|
+
if (files == null)
|
|
172
236
|
return;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return { background: null, layers: [], composite: null };
|
|
185
|
-
const [l, t, w, h] = $crop;
|
|
186
|
-
return $pixi?.get_blobs(
|
|
187
|
-
$pixi.get_layers(),
|
|
188
|
-
new Rectangle(
|
|
189
|
-
Math.round(0),
|
|
190
|
-
Math.round(0),
|
|
191
|
-
Math.round(1 * $dimensions[0]),
|
|
192
|
-
Math.round(1 * $dimensions[1])
|
|
193
|
-
),
|
|
194
|
-
$dimensions
|
|
195
|
-
);
|
|
237
|
+
if (!sources.includes("upload"))
|
|
238
|
+
return;
|
|
239
|
+
const _file = Array.isArray(files) ? files[0] : files;
|
|
240
|
+
await editor.add_image({ image: _file });
|
|
241
|
+
await crop.add_image({ image: _file });
|
|
242
|
+
crop.reset();
|
|
243
|
+
background_image = true;
|
|
244
|
+
handle_tool_change({ tool: "draw" });
|
|
245
|
+
dispatch("upload");
|
|
246
|
+
dispatch("input");
|
|
247
|
+
dispatch("change");
|
|
196
248
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
true,
|
|
204
|
-
orig_canvas_size ? orig_canvas_size : $dimensions
|
|
205
|
-
);
|
|
206
|
-
if (!sources.length) {
|
|
207
|
-
set_tool("draw");
|
|
208
|
-
} else {
|
|
209
|
-
set_tool("bg");
|
|
249
|
+
function handle_tool_change({ tool }) {
|
|
250
|
+
editor.set_tool(tool);
|
|
251
|
+
current_tool = tool;
|
|
252
|
+
if (tool === "image") {
|
|
253
|
+
crop.set_tool("image");
|
|
254
|
+
crop.set_subtool("crop");
|
|
210
255
|
}
|
|
211
|
-
dispatch("clear");
|
|
212
|
-
let _size = (canvas_size ? canvas_size : crop_size) || [800, 600];
|
|
213
|
-
editor_context.reset(true, _size);
|
|
214
256
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
app.resize(width, height2);
|
|
224
|
-
dimensions.set([width, height2]);
|
|
257
|
+
function handle_subtool_change({
|
|
258
|
+
tool,
|
|
259
|
+
subtool
|
|
260
|
+
}) {
|
|
261
|
+
editor.set_subtool(subtool);
|
|
262
|
+
current_subtool = subtool;
|
|
263
|
+
if (subtool === null) {
|
|
264
|
+
return;
|
|
225
265
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
resizer.observe(canvas_wrap);
|
|
233
|
-
resizer.observe(pixi_target);
|
|
234
|
-
for (const k of $contexts) {
|
|
235
|
-
if (k in $init_context && typeof $init_context[k] === "function") {
|
|
236
|
-
$init_context[k]?.($dimensions);
|
|
266
|
+
if (tool === "draw") {
|
|
267
|
+
if (subtool === "size") {
|
|
268
|
+
brush_size_visible = true;
|
|
269
|
+
} else if (subtool === "color") {
|
|
270
|
+
brush_color_visible = true;
|
|
237
271
|
}
|
|
238
272
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
273
|
+
if (tool === "erase" && subtool === "size") {
|
|
274
|
+
eraser_size_visible = true;
|
|
275
|
+
}
|
|
276
|
+
if (tool === "image" && subtool === "paste") {
|
|
277
|
+
process_clipboard();
|
|
278
|
+
}
|
|
279
|
+
if (tool === "image" && subtool === "upload") {
|
|
280
|
+
tick().then(() => {
|
|
281
|
+
disable_click = false;
|
|
282
|
+
open_file_upload();
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
let eraser_size_visible = false;
|
|
287
|
+
let selected_color;
|
|
288
|
+
let selected_size;
|
|
289
|
+
let selected_opacity = 1;
|
|
290
|
+
let selected_eraser_size;
|
|
291
|
+
$: {
|
|
292
|
+
if (brush_options) {
|
|
293
|
+
update_brush_options();
|
|
294
|
+
}
|
|
295
|
+
if (eraser_options) {
|
|
296
|
+
update_eraser_options();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function update_brush_options() {
|
|
300
|
+
const default_color = brush_options.default_color === "auto" ? brush_options.colors[0] : brush_options.default_color;
|
|
301
|
+
if (Array.isArray(default_color)) {
|
|
302
|
+
selected_color = default_color[0];
|
|
303
|
+
selected_opacity = default_color[1];
|
|
304
|
+
} else {
|
|
305
|
+
selected_color = default_color;
|
|
306
|
+
const color = tinycolor(default_color);
|
|
307
|
+
if (color.getAlpha() < 1) {
|
|
308
|
+
selected_opacity = color.getAlpha();
|
|
309
|
+
} else {
|
|
310
|
+
selected_opacity = 1;
|
|
243
311
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
312
|
+
}
|
|
313
|
+
selected_size = typeof brush_options.default_size === "number" ? brush_options.default_size : 25;
|
|
314
|
+
}
|
|
315
|
+
function update_eraser_options() {
|
|
316
|
+
selected_eraser_size = eraser_options.default_size === "auto" ? 25 : eraser_options.default_size;
|
|
317
|
+
}
|
|
318
|
+
let brush_size_visible = false;
|
|
319
|
+
let brush_color_visible = false;
|
|
320
|
+
$:
|
|
321
|
+
brush?.set_brush_color(
|
|
322
|
+
(() => {
|
|
323
|
+
let color_value;
|
|
324
|
+
if (selected_color === "auto") {
|
|
325
|
+
const default_color = brush_options.colors.find(
|
|
326
|
+
(color) => Array.isArray(color) ? color[0] === brush_options.default_color : color === brush_options.default_color
|
|
327
|
+
) || brush_options.colors[0];
|
|
328
|
+
color_value = Array.isArray(default_color) ? default_color[0] : default_color;
|
|
329
|
+
} else {
|
|
330
|
+
color_value = selected_color;
|
|
251
331
|
}
|
|
332
|
+
return color_value;
|
|
333
|
+
})()
|
|
334
|
+
);
|
|
335
|
+
$:
|
|
336
|
+
brush?.set_brush_size(
|
|
337
|
+
typeof selected_size === "number" ? selected_size : 25
|
|
338
|
+
);
|
|
339
|
+
$:
|
|
340
|
+
brush?.set_eraser_size(
|
|
341
|
+
typeof selected_eraser_size === "number" ? selected_eraser_size : 25
|
|
342
|
+
);
|
|
343
|
+
$:
|
|
344
|
+
disable_click = current_tool !== "image" || current_tool === "image" && background_image || current_tool === "image" && current_subtool === "webcam" || !sources.includes("upload");
|
|
345
|
+
let current_subtool = null;
|
|
346
|
+
let preview = false;
|
|
347
|
+
$:
|
|
348
|
+
brush?.preview_brush(preview);
|
|
349
|
+
$:
|
|
350
|
+
brush?.set_brush_opacity(selected_opacity);
|
|
351
|
+
function handle_zoom_change(zoom_level2) {
|
|
352
|
+
zoom.set_zoom(zoom_level2);
|
|
353
|
+
}
|
|
354
|
+
function zoom_in_out(direction) {
|
|
355
|
+
zoom.set_zoom(
|
|
356
|
+
direction === "in" ? zoom_level + (zoom_level < 1 ? 0.1 : zoom_level * 0.1) : zoom_level - (zoom_level < 1 ? 0.1 : zoom_level * 0.1)
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
async function process_clipboard() {
|
|
360
|
+
const items = await navigator.clipboard.read();
|
|
361
|
+
for (let i = 0; i < items.length; i++) {
|
|
362
|
+
const type = items[i].types.find((t) => t.startsWith("image/"));
|
|
363
|
+
if (type) {
|
|
364
|
+
const blob = await items[i].getType(type);
|
|
365
|
+
handle_files(blob);
|
|
252
366
|
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function handle_capture(e) {
|
|
370
|
+
if (e.detail !== null) {
|
|
371
|
+
handle_files(e.detail);
|
|
372
|
+
}
|
|
373
|
+
handle_subtool_change({ tool: current_tool, subtool: null });
|
|
374
|
+
}
|
|
256
375
|
function handle_save() {
|
|
257
|
-
saved_history = $current_history;
|
|
258
376
|
dispatch("save");
|
|
259
377
|
}
|
|
260
|
-
|
|
261
|
-
|
|
378
|
+
$:
|
|
379
|
+
add_image_from_url(composite || background);
|
|
380
|
+
$:
|
|
381
|
+
add_layers_from_url(layers);
|
|
382
|
+
async function handle_crop_confirm() {
|
|
383
|
+
const { image } = await crop.get_crop_bounds();
|
|
384
|
+
if (!image)
|
|
385
|
+
return;
|
|
386
|
+
await editor.add_image({
|
|
387
|
+
image,
|
|
388
|
+
resize: false
|
|
389
|
+
});
|
|
390
|
+
handle_subtool_change({ tool: "image", subtool: null });
|
|
391
|
+
dispatch("change");
|
|
392
|
+
dispatch("input");
|
|
262
393
|
}
|
|
263
394
|
</script>
|
|
264
395
|
|
|
265
|
-
<
|
|
396
|
+
<div
|
|
397
|
+
data-testid="image"
|
|
398
|
+
class="image-container"
|
|
399
|
+
class:dark-bg={current_subtool === "crop"}
|
|
400
|
+
use:drag={{
|
|
401
|
+
on_drag_change: (dragging) => (is_dragging = dragging),
|
|
402
|
+
on_files: handle_files,
|
|
403
|
+
accepted_types: "image/*",
|
|
404
|
+
disable_click: disable_click
|
|
405
|
+
}}
|
|
406
|
+
aria-label={"Click to upload or drop files"}
|
|
407
|
+
aria-dropeffect="copy"
|
|
408
|
+
>
|
|
409
|
+
{#if ready}
|
|
410
|
+
{#if current_subtool !== "crop"}
|
|
411
|
+
<Controls
|
|
412
|
+
{changeable}
|
|
413
|
+
on:set_zoom={(e) => handle_zoom_change(e.detail)}
|
|
414
|
+
on:zoom_in={() => zoom_in_out("in")}
|
|
415
|
+
on:zoom_out={() => zoom_in_out("out")}
|
|
416
|
+
{min_zoom}
|
|
417
|
+
current_zoom={zoom_level}
|
|
418
|
+
on:remove_image={() => {
|
|
419
|
+
editor.reset_canvas();
|
|
420
|
+
handle_tool_change({ tool: "image" });
|
|
421
|
+
background_image = false;
|
|
422
|
+
has_drawn = false;
|
|
423
|
+
}}
|
|
424
|
+
tool={current_tool}
|
|
425
|
+
can_save={true}
|
|
426
|
+
on:save={handle_save}
|
|
427
|
+
on:pan={(e) => {
|
|
428
|
+
handle_tool_change({ tool: "pan" });
|
|
429
|
+
}}
|
|
430
|
+
/>
|
|
431
|
+
{/if}
|
|
266
432
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
433
|
+
{#if current_subtool !== "crop"}
|
|
434
|
+
<Toolbar
|
|
435
|
+
{sources}
|
|
436
|
+
{transforms}
|
|
437
|
+
background={background_image}
|
|
438
|
+
on:tool_change={(e) => handle_tool_change(e.detail)}
|
|
439
|
+
on:subtool_change={(e) => handle_subtool_change(e.detail)}
|
|
440
|
+
show_brush_size={brush_size_visible}
|
|
441
|
+
show_brush_color={brush_color_visible}
|
|
442
|
+
show_eraser_size={eraser_size_visible}
|
|
443
|
+
{brush_options}
|
|
444
|
+
{eraser_options}
|
|
445
|
+
bind:selected_color
|
|
446
|
+
bind:selected_size
|
|
447
|
+
bind:selected_eraser_size
|
|
448
|
+
bind:selected_opacity
|
|
449
|
+
bind:preview
|
|
450
|
+
tool={current_tool}
|
|
451
|
+
subtool={current_subtool}
|
|
452
|
+
/>
|
|
453
|
+
{/if}
|
|
454
|
+
|
|
455
|
+
{#if current_tool === "image" && current_subtool === "webcam"}
|
|
456
|
+
<div class="modal">
|
|
457
|
+
<div class="modal-inner">
|
|
458
|
+
<Webcam
|
|
459
|
+
{upload}
|
|
460
|
+
{root}
|
|
461
|
+
on:capture={handle_capture}
|
|
462
|
+
on:error
|
|
463
|
+
on:drag
|
|
464
|
+
{mirror_webcam}
|
|
465
|
+
streaming={false}
|
|
466
|
+
mode="image"
|
|
467
|
+
include_audio={false}
|
|
468
|
+
{i18n}
|
|
469
|
+
/>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
{/if}
|
|
473
|
+
|
|
474
|
+
{#if current_subtool !== "crop" && !layer_options.disabled}
|
|
475
|
+
<SecondaryToolbar
|
|
476
|
+
enable_additional_layers={layer_options.allow_additional_layers}
|
|
477
|
+
layers={editor.layers}
|
|
478
|
+
on:new_layer={() => {
|
|
479
|
+
editor.add_layer();
|
|
480
|
+
}}
|
|
481
|
+
on:change_layer={(e) => {
|
|
482
|
+
editor.set_layer(e.detail);
|
|
483
|
+
}}
|
|
484
|
+
on:move_layer={(e) => {
|
|
485
|
+
editor.move_layer(e.detail.id, e.detail.direction);
|
|
486
|
+
}}
|
|
487
|
+
on:delete_layer={(e) => {
|
|
488
|
+
editor.delete_layer(e.detail);
|
|
489
|
+
}}
|
|
490
|
+
/>
|
|
491
|
+
{/if}
|
|
492
|
+
{/if}
|
|
493
|
+
<div
|
|
494
|
+
class="pixi-target"
|
|
495
|
+
class:visible={current_subtool !== "crop"}
|
|
496
|
+
bind:this={pixi_target}
|
|
497
|
+
></div>
|
|
498
|
+
<div
|
|
499
|
+
class="pixi-target-crop"
|
|
500
|
+
class:visible={current_subtool === "crop"}
|
|
501
|
+
bind:this={pixi_target_crop}
|
|
502
|
+
></div>
|
|
503
|
+
|
|
504
|
+
{#if current_subtool === "crop"}
|
|
505
|
+
<div class="crop-confirm-button">
|
|
506
|
+
<IconButton
|
|
507
|
+
Icon={Check}
|
|
508
|
+
label="Confirm crop"
|
|
509
|
+
show_label={true}
|
|
510
|
+
size="large"
|
|
511
|
+
padded={true}
|
|
512
|
+
color="white"
|
|
513
|
+
background="var(--color-green-500)"
|
|
514
|
+
label_position="right"
|
|
515
|
+
on:click={handle_crop_confirm}
|
|
516
|
+
/>
|
|
284
517
|
</div>
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
class:no-border={!bg && $active_tool === "bg" && !history}
|
|
288
|
-
style:width="{$editor_box.child_width + 1}px"
|
|
289
|
-
style:height="{$editor_box.child_height + 1}px"
|
|
290
|
-
style:top="{$editor_box.child_top - $editor_box.parent_top - 0.5}px"
|
|
291
|
-
style:left="{$editor_box.child_left - $editor_box.parent_left - 0.5}px"
|
|
292
|
-
></div>
|
|
293
|
-
</div>
|
|
518
|
+
{/if}
|
|
519
|
+
<slot></slot>
|
|
294
520
|
</div>
|
|
295
521
|
|
|
296
522
|
<style>
|
|
297
|
-
.
|
|
523
|
+
.image-container {
|
|
298
524
|
display: flex;
|
|
299
|
-
width: 100%;
|
|
300
525
|
height: 100%;
|
|
301
|
-
|
|
526
|
+
flex-direction: column;
|
|
302
527
|
justify-content: center;
|
|
528
|
+
align-items: center;
|
|
529
|
+
max-height: 100%;
|
|
530
|
+
border-radius: var(--radius-sm);
|
|
303
531
|
}
|
|
304
|
-
|
|
532
|
+
|
|
533
|
+
.pixi-target {
|
|
534
|
+
width: 100%;
|
|
535
|
+
height: 100%;
|
|
305
536
|
position: absolute;
|
|
306
|
-
|
|
537
|
+
top: 0;
|
|
538
|
+
left: 0;
|
|
539
|
+
z-index: 1;
|
|
540
|
+
display: block;
|
|
541
|
+
opacity: 0;
|
|
307
542
|
pointer-events: none;
|
|
308
|
-
border-radius: var(--radius-
|
|
543
|
+
border-radius: var(--radius-sm);
|
|
309
544
|
}
|
|
310
545
|
|
|
311
|
-
.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
546
|
+
.pixi-target-crop {
|
|
547
|
+
width: 100%;
|
|
548
|
+
height: 100%;
|
|
549
|
+
position: absolute;
|
|
550
|
+
top: 0;
|
|
551
|
+
left: 0;
|
|
552
|
+
z-index: 2;
|
|
553
|
+
display: block;
|
|
554
|
+
opacity: 0;
|
|
555
|
+
pointer-events: none;
|
|
556
|
+
border-radius: var(--radius-sm);
|
|
317
557
|
}
|
|
318
558
|
|
|
319
|
-
.
|
|
320
|
-
|
|
559
|
+
.visible {
|
|
560
|
+
opacity: 1;
|
|
561
|
+
pointer-events: auto;
|
|
321
562
|
}
|
|
322
563
|
|
|
323
|
-
.
|
|
324
|
-
|
|
325
|
-
|
|
564
|
+
.pixi-target {
|
|
565
|
+
width: 100%;
|
|
566
|
+
height: 100%;
|
|
567
|
+
position: absolute;
|
|
568
|
+
top: 0;
|
|
569
|
+
left: 0;
|
|
570
|
+
right: 0;
|
|
571
|
+
bottom: 0;
|
|
326
572
|
overflow: hidden;
|
|
327
|
-
height: fit-content;
|
|
328
|
-
width: auto;
|
|
329
573
|
}
|
|
330
574
|
|
|
331
|
-
.
|
|
575
|
+
.modal {
|
|
576
|
+
position: absolute;
|
|
577
|
+
height: 100%;
|
|
578
|
+
width: 100%;
|
|
579
|
+
left: 0;
|
|
580
|
+
right: 0;
|
|
581
|
+
margin: auto;
|
|
582
|
+
z-index: var(--layer-top);
|
|
332
583
|
display: flex;
|
|
333
|
-
justify-content: center;
|
|
334
584
|
align-items: center;
|
|
335
|
-
padding: 0 var(--spacing-xl) 0 0;
|
|
336
|
-
border: 1px solid var(--block-border-color);
|
|
337
|
-
border-radius: var(--radius-sm);
|
|
338
|
-
margin: var(--spacing-xxl) 0 var(--spacing-xxl) 0;
|
|
339
|
-
width: fit-content;
|
|
340
|
-
margin: 0 auto;
|
|
341
585
|
}
|
|
342
586
|
|
|
343
|
-
.
|
|
344
|
-
display: flex;
|
|
587
|
+
.modal-inner {
|
|
345
588
|
height: 100%;
|
|
346
|
-
|
|
347
|
-
|
|
589
|
+
width: 100%;
|
|
590
|
+
background: var(--block-background-fill);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.dark-bg {
|
|
594
|
+
background: #333;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.crop-confirm-button {
|
|
598
|
+
position: absolute;
|
|
599
|
+
bottom: 8px;
|
|
600
|
+
left: 0;
|
|
601
|
+
right: 0;
|
|
602
|
+
margin: auto;
|
|
603
|
+
z-index: var(--layer-top);
|
|
604
|
+
display: flex;
|
|
348
605
|
align-items: center;
|
|
349
|
-
|
|
606
|
+
justify-content: center;
|
|
350
607
|
}
|
|
351
608
|
</style>
|