@gradio/imageeditor 0.12.2 → 0.12.4
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 +20 -0
- package/ImageEditor.stories.svelte +2 -0
- package/Index.svelte +25 -4
- package/dist/Index.svelte +19 -3
- package/dist/Index.svelte.d.ts +8 -4
- package/dist/shared/ImageEditor.svelte +18 -16
- package/dist/shared/ImageEditor.svelte.d.ts +1 -0
- package/dist/shared/InteractiveImageEditor.svelte +16 -3
- package/dist/shared/InteractiveImageEditor.svelte.d.ts +5 -2
- package/dist/shared/tools/Crop.svelte +2 -2
- package/dist/shared/tools/Sources.svelte +9 -4
- package/dist/shared/tools/Sources.svelte.d.ts +2 -1
- package/dist/shared/tools/crop.d.ts +1 -1
- package/dist/shared/tools/crop.js +3 -3
- package/dist/shared/tools/sources.d.ts +13 -7
- package/dist/shared/tools/sources.js +60 -28
- package/package.json +5 -5
- package/shared/ImageEditor.svelte +18 -16
- package/shared/InteractiveImageEditor.svelte +19 -5
- package/shared/tools/Crop.svelte +2 -2
- package/shared/tools/Sources.svelte +9 -4
- package/shared/tools/crop.ts +3 -5
- package/shared/tools/sources.ts +85 -41
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @gradio/imageeditor
|
|
2
2
|
|
|
3
|
+
## 0.12.4
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- [#10370](https://github.com/gradio-app/gradio/pull/10370) [`71c8b8a`](https://github.com/gradio-app/gradio/commit/71c8b8abbfae0e47eb46561b0104bd452e5dffe8) - Clear Image Editor Value with None. Thanks @freddyaboulton!
|
|
8
|
+
- [#10416](https://github.com/gradio-app/gradio/pull/10416) [`3c2e12b`](https://github.com/gradio-app/gradio/commit/3c2e12b5ba52441f45565ae8e14cea46ea5b5b2c) - Fix ImageEditor Cropping - Cropping now crops the background image instead of the image + canvas. Thanks @freddyaboulton!
|
|
9
|
+
|
|
10
|
+
## 0.12.3
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
- [#10357](https://github.com/gradio-app/gradio/pull/10357) [`43e7cce`](https://github.com/gradio-app/gradio/commit/43e7cce2bd8ddd274fcba890bfeaa7ead7f32434) - Fix ImageEditor Size Issues. Thanks @freddyaboulton!
|
|
15
|
+
|
|
16
|
+
### Dependency updates
|
|
17
|
+
|
|
18
|
+
- @gradio/upload@0.14.6
|
|
19
|
+
- @gradio/wasm@0.17.0
|
|
20
|
+
- @gradio/statustracker@0.10.2
|
|
21
|
+
- @gradio/image@0.20.2
|
|
22
|
+
|
|
3
23
|
## 0.12.2
|
|
4
24
|
|
|
5
25
|
### Fixes
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
interactive: "true",
|
|
43
43
|
label: "Image Editor",
|
|
44
44
|
show_label: true,
|
|
45
|
+
canvas_size: [800, 600],
|
|
45
46
|
brush: {
|
|
46
47
|
default_size: "auto",
|
|
47
48
|
colors: ["#ff0000", "#00ff00", "#0000ff"],
|
|
@@ -216,6 +217,7 @@
|
|
|
216
217
|
type: "pil",
|
|
217
218
|
placeholder: "Upload an image of a cat",
|
|
218
219
|
sources: ["upload", "webcam"],
|
|
220
|
+
canvas_size: [800, 800],
|
|
219
221
|
interactive: "true",
|
|
220
222
|
brush: {
|
|
221
223
|
default_size: "auto",
|
package/Index.svelte
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
export let root: string;
|
|
30
30
|
export let value_is_output = false;
|
|
31
31
|
|
|
32
|
-
export let height: number | undefined
|
|
32
|
+
export let height: number | undefined;
|
|
33
33
|
export let width: number | undefined;
|
|
34
34
|
|
|
35
35
|
export let _selectable = false;
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
export let server: {
|
|
56
56
|
accept_blobs: (a: any) => void;
|
|
57
57
|
};
|
|
58
|
-
export let canvas_size: [number, number]
|
|
58
|
+
export let canvas_size: [number, number];
|
|
59
|
+
export let fixed_canvas = false;
|
|
59
60
|
export let show_fullscreen_button = true;
|
|
60
61
|
export let full_history: any = null;
|
|
61
62
|
|
|
@@ -125,6 +126,18 @@
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
let dynamic_height: number | undefined = undefined;
|
|
130
|
+
|
|
131
|
+
// In case no height given, pick a height large enough for the entire canvas
|
|
132
|
+
// in pixi.ts, the max-height of the canvas is canvas height / pixel ratio
|
|
133
|
+
|
|
134
|
+
let safe_height_initial = Math.max(
|
|
135
|
+
canvas_size[1] / (is_browser ? window.devicePixelRatio : 1),
|
|
136
|
+
250
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
$: safe_height = Math.max((dynamic_height ?? safe_height_initial) + 100, 250);
|
|
140
|
+
|
|
128
141
|
$: has_value = value?.background || value?.layers?.length || value?.composite;
|
|
129
142
|
</script>
|
|
130
143
|
|
|
@@ -136,7 +149,7 @@
|
|
|
136
149
|
padding={false}
|
|
137
150
|
{elem_id}
|
|
138
151
|
{elem_classes}
|
|
139
|
-
|
|
152
|
+
{height}
|
|
140
153
|
{width}
|
|
141
154
|
allow_overflow={false}
|
|
142
155
|
{container}
|
|
@@ -171,7 +184,7 @@
|
|
|
171
184
|
padding={false}
|
|
172
185
|
{elem_id}
|
|
173
186
|
{elem_classes}
|
|
174
|
-
height={height ||
|
|
187
|
+
height={height || safe_height}
|
|
175
188
|
{width}
|
|
176
189
|
allow_overflow={false}
|
|
177
190
|
{container}
|
|
@@ -194,11 +207,13 @@
|
|
|
194
207
|
{crop_size}
|
|
195
208
|
{value}
|
|
196
209
|
bind:this={editor_instance}
|
|
210
|
+
bind:dynamic_height
|
|
197
211
|
{root}
|
|
198
212
|
{sources}
|
|
199
213
|
{label}
|
|
200
214
|
{show_label}
|
|
201
215
|
{height}
|
|
216
|
+
{fixed_canvas}
|
|
202
217
|
on:save={(e) => handle_save()}
|
|
203
218
|
on:edit={() => gradio.dispatch("edit")}
|
|
204
219
|
on:clear={() => gradio.dispatch("clear")}
|
|
@@ -210,6 +225,12 @@
|
|
|
210
225
|
loading_status.status = "error";
|
|
211
226
|
gradio.dispatch("error", detail);
|
|
212
227
|
}}
|
|
228
|
+
on:receive_null={() =>
|
|
229
|
+
(value = {
|
|
230
|
+
background: null,
|
|
231
|
+
layers: [],
|
|
232
|
+
composite: null
|
|
233
|
+
})}
|
|
213
234
|
on:error
|
|
214
235
|
{brush}
|
|
215
236
|
{eraser}
|
package/dist/Index.svelte
CHANGED
|
@@ -18,7 +18,7 @@ export let show_label;
|
|
|
18
18
|
export let show_download_button;
|
|
19
19
|
export let root;
|
|
20
20
|
export let value_is_output = false;
|
|
21
|
-
export let height
|
|
21
|
+
export let height;
|
|
22
22
|
export let width;
|
|
23
23
|
export let _selectable = false;
|
|
24
24
|
export let container = true;
|
|
@@ -41,6 +41,7 @@ export let layers = true;
|
|
|
41
41
|
export let attached_events = [];
|
|
42
42
|
export let server;
|
|
43
43
|
export let canvas_size;
|
|
44
|
+
export let fixed_canvas = false;
|
|
44
45
|
export let show_fullscreen_button = true;
|
|
45
46
|
export let full_history = null;
|
|
46
47
|
export let gradio;
|
|
@@ -81,6 +82,13 @@ function handle_history_change() {
|
|
|
81
82
|
tick().then((_) => value_is_output = false);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
85
|
+
let dynamic_height = void 0;
|
|
86
|
+
let safe_height_initial = Math.max(
|
|
87
|
+
canvas_size[1] / (is_browser ? window.devicePixelRatio : 1),
|
|
88
|
+
250
|
|
89
|
+
);
|
|
90
|
+
$:
|
|
91
|
+
safe_height = Math.max((dynamic_height ?? safe_height_initial) + 100, 250);
|
|
84
92
|
$:
|
|
85
93
|
has_value = value?.background || value?.layers?.length || value?.composite;
|
|
86
94
|
</script>
|
|
@@ -93,7 +101,7 @@ $:
|
|
|
93
101
|
padding={false}
|
|
94
102
|
{elem_id}
|
|
95
103
|
{elem_classes}
|
|
96
|
-
|
|
104
|
+
{height}
|
|
97
105
|
{width}
|
|
98
106
|
allow_overflow={false}
|
|
99
107
|
{container}
|
|
@@ -128,7 +136,7 @@ $:
|
|
|
128
136
|
padding={false}
|
|
129
137
|
{elem_id}
|
|
130
138
|
{elem_classes}
|
|
131
|
-
height={height ||
|
|
139
|
+
height={height || safe_height}
|
|
132
140
|
{width}
|
|
133
141
|
allow_overflow={false}
|
|
134
142
|
{container}
|
|
@@ -151,11 +159,13 @@ $:
|
|
|
151
159
|
{crop_size}
|
|
152
160
|
{value}
|
|
153
161
|
bind:this={editor_instance}
|
|
162
|
+
bind:dynamic_height
|
|
154
163
|
{root}
|
|
155
164
|
{sources}
|
|
156
165
|
{label}
|
|
157
166
|
{show_label}
|
|
158
167
|
{height}
|
|
168
|
+
{fixed_canvas}
|
|
159
169
|
on:save={(e) => handle_save()}
|
|
160
170
|
on:edit={() => gradio.dispatch("edit")}
|
|
161
171
|
on:clear={() => gradio.dispatch("clear")}
|
|
@@ -167,6 +177,12 @@ $:
|
|
|
167
177
|
loading_status.status = "error";
|
|
168
178
|
gradio.dispatch("error", detail);
|
|
169
179
|
}}
|
|
180
|
+
on:receive_null={() =>
|
|
181
|
+
(value = {
|
|
182
|
+
background: null,
|
|
183
|
+
layers: [],
|
|
184
|
+
composite: null
|
|
185
|
+
})}
|
|
170
186
|
on:error
|
|
171
187
|
{brush}
|
|
172
188
|
{eraser}
|
package/dist/Index.svelte.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ declare const __propDef: {
|
|
|
14
14
|
show_download_button: boolean;
|
|
15
15
|
root: string;
|
|
16
16
|
value_is_output?: boolean | undefined;
|
|
17
|
-
height
|
|
17
|
+
height: number | undefined;
|
|
18
18
|
width: number | undefined;
|
|
19
19
|
_selectable?: boolean | undefined;
|
|
20
20
|
container?: boolean | undefined;
|
|
@@ -34,7 +34,8 @@ declare const __propDef: {
|
|
|
34
34
|
server: {
|
|
35
35
|
accept_blobs: (a: any) => void;
|
|
36
36
|
};
|
|
37
|
-
canvas_size: [number, number]
|
|
37
|
+
canvas_size: [number, number];
|
|
38
|
+
fixed_canvas?: boolean | undefined;
|
|
38
39
|
show_fullscreen_button?: boolean | undefined;
|
|
39
40
|
full_history?: any;
|
|
40
41
|
gradio: Gradio<{
|
|
@@ -153,9 +154,12 @@ export default class Index extends SvelteComponent<IndexProps, IndexEvents, Inde
|
|
|
153
154
|
set server(_: {
|
|
154
155
|
accept_blobs: (a: any) => void;
|
|
155
156
|
});
|
|
156
|
-
get canvas_size(): [number, number]
|
|
157
|
+
get canvas_size(): [number, number];
|
|
157
158
|
/**accessor*/
|
|
158
|
-
set canvas_size(_: [number, number]
|
|
159
|
+
set canvas_size(_: [number, number]);
|
|
160
|
+
get fixed_canvas(): boolean | undefined;
|
|
161
|
+
/**accessor*/
|
|
162
|
+
set fixed_canvas(_: boolean | undefined);
|
|
159
163
|
get show_fullscreen_button(): boolean | undefined;
|
|
160
164
|
/**accessor*/
|
|
161
165
|
set show_fullscreen_button(_: boolean | undefined);
|
|
@@ -42,8 +42,11 @@ let editor_box = writable({
|
|
|
42
42
|
child_right: 0,
|
|
43
43
|
child_bottom: 0
|
|
44
44
|
});
|
|
45
|
+
export let canvas_height = void 0;
|
|
45
46
|
$:
|
|
46
47
|
height = $editor_box.child_height;
|
|
48
|
+
$:
|
|
49
|
+
canvas_height = $editor_box.child_height + 1;
|
|
47
50
|
const crop = writable([0, 0, 1, 1]);
|
|
48
51
|
const position_spring = spring(
|
|
49
52
|
{ x: 0, y: 0 },
|
|
@@ -168,10 +171,10 @@ function reposition_canvas() {
|
|
|
168
171
|
if (!$editor_box)
|
|
169
172
|
return;
|
|
170
173
|
const [l, t, w, h] = $crop;
|
|
171
|
-
const cx =
|
|
172
|
-
const cy =
|
|
173
|
-
const cw =
|
|
174
|
-
const ch =
|
|
174
|
+
const cx = 0;
|
|
175
|
+
const cy = 0;
|
|
176
|
+
const cw = $editor_box.child_width;
|
|
177
|
+
const ch = $editor_box.child_height;
|
|
175
178
|
const x = 0.5 * $editor_box.child_width - cx - cw / 2;
|
|
176
179
|
const y = 0.5 * $editor_box.child_height - cy - ch / 2;
|
|
177
180
|
position_spring.set({ x, y });
|
|
@@ -183,10 +186,10 @@ export async function get_blobs() {
|
|
|
183
186
|
return $pixi?.get_blobs(
|
|
184
187
|
$pixi.get_layers(),
|
|
185
188
|
new Rectangle(
|
|
186
|
-
Math.round(
|
|
187
|
-
Math.round(
|
|
188
|
-
Math.round(
|
|
189
|
-
Math.round(
|
|
189
|
+
Math.round(0),
|
|
190
|
+
Math.round(0),
|
|
191
|
+
Math.round(1 * $dimensions[0]),
|
|
192
|
+
Math.round(1 * $dimensions[1])
|
|
190
193
|
),
|
|
191
194
|
$dimensions
|
|
192
195
|
);
|
|
@@ -282,14 +285,10 @@ export function set_tool(tool) {
|
|
|
282
285
|
<div
|
|
283
286
|
class="canvas"
|
|
284
287
|
class:no-border={!bg && $active_tool === "bg" && !history}
|
|
285
|
-
style:width="{$
|
|
286
|
-
style:height="{$
|
|
287
|
-
style:top="{$
|
|
288
|
-
|
|
289
|
-
0.5}px"
|
|
290
|
-
style:left="{$crop[0] * $editor_box.child_width +
|
|
291
|
-
($editor_box.child_left - $editor_box.parent_left) -
|
|
292
|
-
0.5}px"
|
|
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"
|
|
293
292
|
></div>
|
|
294
293
|
</div>
|
|
295
294
|
</div>
|
|
@@ -312,6 +311,9 @@ export function set_tool(tool) {
|
|
|
312
311
|
.container {
|
|
313
312
|
position: relative;
|
|
314
313
|
margin: var(--spacing-md);
|
|
314
|
+
/* in case the canvas_size is really small */
|
|
315
|
+
/* set min-height so that upload text does not cover the toolbar */
|
|
316
|
+
min-height: 100px;
|
|
315
317
|
}
|
|
316
318
|
|
|
317
319
|
.no-border {
|
|
@@ -54,6 +54,7 @@ declare const __propDef: {
|
|
|
54
54
|
parent_height: number;
|
|
55
55
|
full_history?: (CommandNode | null) | undefined;
|
|
56
56
|
height?: number | undefined;
|
|
57
|
+
canvas_height?: number | undefined;
|
|
57
58
|
get_blobs?: (() => Promise<ImageBlobs>) | undefined;
|
|
58
59
|
handle_remove?: (() => void) | undefined;
|
|
59
60
|
set_tool?: ((tool: tool) => void) | undefined;
|
|
@@ -31,12 +31,14 @@ export let layers;
|
|
|
31
31
|
export let accept_blobs;
|
|
32
32
|
export let status = "complete";
|
|
33
33
|
export let canvas_size;
|
|
34
|
+
export let fixed_canvas = false;
|
|
34
35
|
export let realtime;
|
|
35
36
|
export let upload;
|
|
36
37
|
export let stream_handler;
|
|
37
38
|
export let dragging;
|
|
38
39
|
export let placeholder = void 0;
|
|
39
|
-
export let
|
|
40
|
+
export let dynamic_height = void 0;
|
|
41
|
+
export let height;
|
|
40
42
|
export let full_history = null;
|
|
41
43
|
const dispatch = createEventDispatcher();
|
|
42
44
|
let editor;
|
|
@@ -50,7 +52,12 @@ $:
|
|
|
50
52
|
if (bg)
|
|
51
53
|
dispatch("upload");
|
|
52
54
|
export async function get_data() {
|
|
53
|
-
|
|
55
|
+
let blobs;
|
|
56
|
+
try {
|
|
57
|
+
blobs = await editor.get_blobs();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return { background: null, layers: [], composite: null };
|
|
60
|
+
}
|
|
54
61
|
const bg2 = blobs.background ? upload(
|
|
55
62
|
await prepare_files([new File([blobs.background], "background.png")]),
|
|
56
63
|
root
|
|
@@ -78,6 +85,7 @@ function handle_value(value2) {
|
|
|
78
85
|
return;
|
|
79
86
|
if (value2 == null) {
|
|
80
87
|
editor.handle_remove();
|
|
88
|
+
dispatch("receive_null");
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
$:
|
|
@@ -149,6 +157,9 @@ async function handle_change(e) {
|
|
|
149
157
|
}
|
|
150
158
|
let active_mode = null;
|
|
151
159
|
let editor_height = height - 100;
|
|
160
|
+
let _dynamic_height;
|
|
161
|
+
$:
|
|
162
|
+
dynamic_height = _dynamic_height;
|
|
152
163
|
$:
|
|
153
164
|
[heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
|
|
154
165
|
</script>
|
|
@@ -164,6 +175,7 @@ $:
|
|
|
164
175
|
crop_size={Array.isArray(crop_size) ? crop_size : undefined}
|
|
165
176
|
bind:this={editor}
|
|
166
177
|
bind:height={editor_height}
|
|
178
|
+
bind:canvas_height={_dynamic_height}
|
|
167
179
|
parent_height={height}
|
|
168
180
|
{changeable}
|
|
169
181
|
on:save
|
|
@@ -185,10 +197,11 @@ $:
|
|
|
185
197
|
{sources}
|
|
186
198
|
{upload}
|
|
187
199
|
{stream_handler}
|
|
200
|
+
{canvas_size}
|
|
188
201
|
bind:bg
|
|
189
202
|
bind:active_mode
|
|
190
203
|
background_file={value?.background || value?.composite || null}
|
|
191
|
-
|
|
204
|
+
{fixed_canvas}
|
|
192
205
|
></Sources>
|
|
193
206
|
|
|
194
207
|
{#if transforms.includes("crop")}
|
|
@@ -30,13 +30,15 @@ declare const __propDef: {
|
|
|
30
30
|
layers: boolean;
|
|
31
31
|
accept_blobs: (a: any) => void;
|
|
32
32
|
status?: ("pending" | "complete" | "error" | "generating" | "streaming") | undefined;
|
|
33
|
-
canvas_size: [number, number]
|
|
33
|
+
canvas_size: [number, number];
|
|
34
|
+
fixed_canvas?: boolean | undefined;
|
|
34
35
|
realtime: boolean;
|
|
35
36
|
upload: Client["upload"];
|
|
36
37
|
stream_handler: Client["stream"];
|
|
37
38
|
dragging: boolean;
|
|
38
39
|
placeholder?: string | undefined;
|
|
39
|
-
|
|
40
|
+
dynamic_height?: number | undefined;
|
|
41
|
+
height: any;
|
|
40
42
|
full_history?: (CommandNode | null) | undefined;
|
|
41
43
|
get_data?: (() => Promise<ImageBlobs>) | undefined;
|
|
42
44
|
image_id?: (null | string) | undefined;
|
|
@@ -47,6 +49,7 @@ declare const __propDef: {
|
|
|
47
49
|
clear?: CustomEvent<undefined> | undefined;
|
|
48
50
|
upload?: CustomEvent<undefined> | undefined;
|
|
49
51
|
change?: CustomEvent<undefined> | undefined;
|
|
52
|
+
receive_null?: CustomEvent<undefined> | undefined;
|
|
50
53
|
} & {
|
|
51
54
|
[evt: string]: CustomEvent<any>;
|
|
52
55
|
};
|
|
@@ -52,7 +52,7 @@ async function initial_crop() {
|
|
|
52
52
|
h_p = new_height / $editor_box.child_height;
|
|
53
53
|
l_p = x_offset / $editor_box.child_width;
|
|
54
54
|
t_p = y_offset / $editor_box.child_height;
|
|
55
|
-
c = crop_canvas($pixi.renderer, $pixi.
|
|
55
|
+
c = crop_canvas($pixi.renderer, $pixi.background_container, crop, 0.2);
|
|
56
56
|
c.start(...$dimensions, current_crop, false);
|
|
57
57
|
c.continue(
|
|
58
58
|
[
|
|
@@ -83,7 +83,7 @@ function handle_crop(type, {
|
|
|
83
83
|
}
|
|
84
84
|
cropper = crop_canvas(
|
|
85
85
|
$pixi?.renderer,
|
|
86
|
-
$pixi.
|
|
86
|
+
$pixi.background_container,
|
|
87
87
|
crop,
|
|
88
88
|
current_opacity
|
|
89
89
|
);
|
|
@@ -24,7 +24,8 @@ export let i18n;
|
|
|
24
24
|
export let upload;
|
|
25
25
|
export let stream_handler;
|
|
26
26
|
export let dragging;
|
|
27
|
-
export let
|
|
27
|
+
export let canvas_size;
|
|
28
|
+
export let fixed_canvas = false;
|
|
28
29
|
const { active_tool } = getContext(TOOL_KEY);
|
|
29
30
|
const { pixi, dimensions, register_context, reset, editor_box } = getContext(EDITOR_KEY);
|
|
30
31
|
export let active_mode = null;
|
|
@@ -90,7 +91,8 @@ async function set_background() {
|
|
|
90
91
|
$pixi.renderer,
|
|
91
92
|
background,
|
|
92
93
|
$pixi.resize,
|
|
93
|
-
|
|
94
|
+
canvas_size,
|
|
95
|
+
fixed_canvas
|
|
94
96
|
);
|
|
95
97
|
$dimensions = await add_image.start();
|
|
96
98
|
if (should_reset) {
|
|
@@ -161,8 +163,11 @@ register_context("bg", {
|
|
|
161
163
|
class:click-disabled={!!bg ||
|
|
162
164
|
active_mode === "webcam" ||
|
|
163
165
|
$active_tool !== "bg"}
|
|
164
|
-
style:height="{
|
|
165
|
-
|
|
166
|
+
style:height="{Math.max(
|
|
167
|
+
$editor_box.child_height +
|
|
168
|
+
($editor_box.child_top - $editor_box.parent_top),
|
|
169
|
+
100
|
|
170
|
+
)}px"
|
|
166
171
|
>
|
|
167
172
|
<Upload
|
|
168
173
|
hidden={bg || active_mode === "webcam" || $active_tool !== "bg"}
|
|
@@ -12,7 +12,8 @@ declare const __propDef: {
|
|
|
12
12
|
upload: Client["upload"];
|
|
13
13
|
stream_handler: Client["stream"];
|
|
14
14
|
dragging: boolean;
|
|
15
|
-
|
|
15
|
+
canvas_size: [number, number];
|
|
16
|
+
fixed_canvas?: boolean | undefined;
|
|
16
17
|
active_mode?: ("webcam" | "color" | null) | undefined;
|
|
17
18
|
bg?: boolean | undefined;
|
|
18
19
|
};
|
|
@@ -6,7 +6,7 @@ export interface CropCommand extends Command {
|
|
|
6
6
|
stop: () => number;
|
|
7
7
|
continue: (crop_size: [number, number, number, number], preview?: boolean) => void;
|
|
8
8
|
}
|
|
9
|
-
export declare function crop_canvas(renderer: IRenderer,
|
|
9
|
+
export declare function crop_canvas(renderer: IRenderer, background_container: Container, crop: Writable<[number, number, number, number]>, current_opacity?: number): CropCommand;
|
|
10
10
|
export declare function resize_and_reposition(original_width: number, original_height: number, anchor: "t" | "r" | "l" | "b" | "tl" | "tr" | "bl" | "br" | "c", aspect_ratio: number, max_width: number, max_height: number): {
|
|
11
11
|
new_width: number;
|
|
12
12
|
new_height: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Graphics, Sprite, RenderTexture } from "pixi.js";
|
|
2
2
|
import {} from "../utils/commands";
|
|
3
3
|
import { spring } from "svelte/motion";
|
|
4
|
-
export function crop_canvas(renderer,
|
|
4
|
+
export function crop_canvas(renderer, background_container, crop, current_opacity = 0) {
|
|
5
5
|
let text;
|
|
6
6
|
let sprite;
|
|
7
7
|
const mask_graphics = new Graphics();
|
|
@@ -45,7 +45,7 @@ export function crop_canvas(renderer, mask_container, crop, current_opacity = 0)
|
|
|
45
45
|
});
|
|
46
46
|
crop_mask(_width, _height, _previous_crop, _preview);
|
|
47
47
|
sprite = new Sprite(text);
|
|
48
|
-
|
|
48
|
+
background_container.mask = sprite;
|
|
49
49
|
width = _width;
|
|
50
50
|
height = _height;
|
|
51
51
|
if (set_previous)
|
|
@@ -87,7 +87,7 @@ export function crop_canvas(renderer, mask_container, crop, current_opacity = 0)
|
|
|
87
87
|
}
|
|
88
88
|
else {
|
|
89
89
|
if (!stopped) {
|
|
90
|
-
alpha_spring.set(0);
|
|
90
|
+
alpha_spring.set(0, { hard: true });
|
|
91
91
|
}
|
|
92
92
|
crop.set([
|
|
93
93
|
final_crop[0] / width,
|
|
@@ -8,14 +8,20 @@ interface BgImageCommand extends Command {
|
|
|
8
8
|
start: () => Promise<[number, number]>;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @returns
|
|
11
|
+
* Calculates new dimensions and position for an image to fit within a canvas while maintaining aspect ratio
|
|
12
|
+
* @param image_width Original width of the image
|
|
13
|
+
* @param image_height Original height of the image
|
|
14
|
+
* @param canvas_width Width of the canvas
|
|
15
|
+
* @param canvas_height Height of the canvas
|
|
16
|
+
* @returns Object containing new dimensions and position
|
|
17
17
|
*/
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function fit_image_to_canvas(image_width: number, image_height: number, canvas_width: number, canvas_height: number): {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function add_bg_image(container: Container, renderer: IRenderer, background: Blob | File, resize: (width: number, height: number) => void, canvas_size: [number, number], fixed_canvas: boolean): BgImageCommand;
|
|
19
25
|
/**
|
|
20
26
|
* Command that sets a background
|
|
21
27
|
*/
|
|
@@ -2,46 +2,78 @@ import { Sprite, Texture, DisplayObject, Color, RenderTexture } from "pixi.js";
|
|
|
2
2
|
import {} from "../utils/commands";
|
|
3
3
|
import { make_graphics } from "../utils/pixi";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* @param
|
|
7
|
-
* @param
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
10
|
-
* @returns
|
|
5
|
+
* Calculates new dimensions and position for an image to fit within a canvas while maintaining aspect ratio
|
|
6
|
+
* @param image_width Original width of the image
|
|
7
|
+
* @param image_height Original height of the image
|
|
8
|
+
* @param canvas_width Width of the canvas
|
|
9
|
+
* @param canvas_height Height of the canvas
|
|
10
|
+
* @returns Object containing new dimensions and position
|
|
11
11
|
*/
|
|
12
|
-
export function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
export function fit_image_to_canvas(image_width, image_height, canvas_width, canvas_height) {
|
|
13
|
+
const image_aspect_ratio = image_width / image_height;
|
|
14
|
+
const canvas_aspect_ratio = canvas_width / canvas_height;
|
|
15
|
+
let new_width;
|
|
16
|
+
let new_height;
|
|
17
|
+
if (image_width <= canvas_width && image_height <= canvas_height) {
|
|
18
|
+
new_width = image_width;
|
|
19
|
+
new_height = image_height;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
if (image_aspect_ratio > canvas_aspect_ratio) {
|
|
23
|
+
// Width is the limiting factor
|
|
24
|
+
new_width = canvas_width;
|
|
25
|
+
new_height = canvas_width / image_aspect_ratio;
|
|
23
26
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
else {
|
|
28
|
+
// Height is the limiting factor
|
|
29
|
+
new_height = canvas_height;
|
|
30
|
+
new_width = canvas_height * image_aspect_ratio;
|
|
28
31
|
}
|
|
29
|
-
return [Math.round(new_width), Math.round(new_height)];
|
|
30
32
|
}
|
|
33
|
+
// Calculate position to center the image
|
|
34
|
+
const x = Math.round((canvas_width - new_width) / 2);
|
|
35
|
+
const y = Math.round((canvas_height - new_height) / 2);
|
|
36
|
+
return {
|
|
37
|
+
width: Math.round(new_width),
|
|
38
|
+
height: Math.round(new_height),
|
|
39
|
+
x,
|
|
40
|
+
y
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function add_bg_image(container, renderer, background, resize, canvas_size, fixed_canvas) {
|
|
44
|
+
let sprite;
|
|
31
45
|
return {
|
|
32
46
|
async start() {
|
|
33
47
|
container.removeChildren();
|
|
34
48
|
const img = await createImageBitmap(background);
|
|
35
49
|
const bitmap_texture = Texture.from(img);
|
|
36
50
|
sprite = new Sprite(bitmap_texture);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
if (!fixed_canvas) {
|
|
52
|
+
canvas_size = [sprite.width, sprite.height];
|
|
53
|
+
}
|
|
54
|
+
if (fixed_canvas) {
|
|
55
|
+
const [canvas_width, canvas_height] = canvas_size;
|
|
56
|
+
const { width, height, x, y } = fit_image_to_canvas(sprite.width, sprite.height, canvas_width, canvas_height);
|
|
57
|
+
sprite.width = width;
|
|
58
|
+
sprite.height = height;
|
|
59
|
+
sprite.x = x;
|
|
60
|
+
sprite.y = y;
|
|
61
|
+
return canvas_size;
|
|
62
|
+
}
|
|
63
|
+
// Use existing max_height based scaling if no canvas_size
|
|
64
|
+
const x = fit_image_to_canvas(sprite.width, sprite.height, canvas_size[0], canvas_size[1]);
|
|
65
|
+
sprite.width = x.width;
|
|
66
|
+
sprite.height = x.height;
|
|
67
|
+
sprite.x = 0;
|
|
68
|
+
sprite.y = 0;
|
|
69
|
+
return [x.width, x.height];
|
|
41
70
|
},
|
|
42
71
|
async execute() {
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
resize(
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
fixed_canvas ? canvas_size[0] : sprite.width,
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
fixed_canvas ? canvas_size[1] : sprite.height);
|
|
45
77
|
sprite.zIndex = 0;
|
|
46
78
|
container.addChild(sprite);
|
|
47
79
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/imageeditor",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
"tinycolor2": "^1.6.0",
|
|
13
13
|
"@gradio/atoms": "^0.13.1",
|
|
14
14
|
"@gradio/client": "^1.10.0",
|
|
15
|
-
"@gradio/image": "^0.20.1",
|
|
16
15
|
"@gradio/icons": "^0.10.0",
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/
|
|
16
|
+
"@gradio/image": "^0.20.2",
|
|
17
|
+
"@gradio/statustracker": "^0.10.2",
|
|
19
18
|
"@gradio/utils": "^0.10.0",
|
|
20
|
-
"@gradio/wasm": "^0.
|
|
19
|
+
"@gradio/wasm": "^0.17.0",
|
|
20
|
+
"@gradio/upload": "^0.14.6"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@gradio/preview": "^0.13.0"
|
|
@@ -100,7 +100,10 @@
|
|
|
100
100
|
child_bottom: 0
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
export let canvas_height = undefined;
|
|
104
|
+
|
|
103
105
|
$: height = $editor_box.child_height;
|
|
106
|
+
$: canvas_height = $editor_box.child_height + 1;
|
|
104
107
|
|
|
105
108
|
const crop = writable<[number, number, number, number]>([0, 0, 1, 1]);
|
|
106
109
|
const position_spring = spring(
|
|
@@ -247,10 +250,10 @@
|
|
|
247
250
|
if (!$editor_box) return;
|
|
248
251
|
const [l, t, w, h] = $crop;
|
|
249
252
|
|
|
250
|
-
const cx =
|
|
251
|
-
const cy =
|
|
252
|
-
const cw =
|
|
253
|
-
const ch =
|
|
253
|
+
const cx = 0;
|
|
254
|
+
const cy = 0;
|
|
255
|
+
const cw = $editor_box.child_width;
|
|
256
|
+
const ch = $editor_box.child_height;
|
|
254
257
|
|
|
255
258
|
const x = 0.5 * $editor_box.child_width - cx - cw / 2;
|
|
256
259
|
const y = 0.5 * $editor_box.child_height - cy - ch / 2;
|
|
@@ -266,10 +269,10 @@
|
|
|
266
269
|
return $pixi?.get_blobs(
|
|
267
270
|
$pixi.get_layers(),
|
|
268
271
|
new Rectangle(
|
|
269
|
-
Math.round(
|
|
270
|
-
Math.round(
|
|
271
|
-
Math.round(
|
|
272
|
-
Math.round(
|
|
272
|
+
Math.round(0),
|
|
273
|
+
Math.round(0),
|
|
274
|
+
Math.round(1 * $dimensions[0]),
|
|
275
|
+
Math.round(1 * $dimensions[1])
|
|
273
276
|
),
|
|
274
277
|
$dimensions
|
|
275
278
|
);
|
|
@@ -378,14 +381,10 @@
|
|
|
378
381
|
<div
|
|
379
382
|
class="canvas"
|
|
380
383
|
class:no-border={!bg && $active_tool === "bg" && !history}
|
|
381
|
-
style:width="{$
|
|
382
|
-
style:height="{$
|
|
383
|
-
style:top="{$
|
|
384
|
-
|
|
385
|
-
0.5}px"
|
|
386
|
-
style:left="{$crop[0] * $editor_box.child_width +
|
|
387
|
-
($editor_box.child_left - $editor_box.parent_left) -
|
|
388
|
-
0.5}px"
|
|
384
|
+
style:width="{$editor_box.child_width + 1}px"
|
|
385
|
+
style:height="{$editor_box.child_height + 1}px"
|
|
386
|
+
style:top="{$editor_box.child_top - $editor_box.parent_top - 0.5}px"
|
|
387
|
+
style:left="{$editor_box.child_left - $editor_box.parent_left - 0.5}px"
|
|
389
388
|
></div>
|
|
390
389
|
</div>
|
|
391
390
|
</div>
|
|
@@ -408,6 +407,9 @@
|
|
|
408
407
|
.container {
|
|
409
408
|
position: relative;
|
|
410
409
|
margin: var(--spacing-md);
|
|
410
|
+
/* in case the canvas_size is really small */
|
|
411
|
+
/* set min-height so that upload text does not cover the toolbar */
|
|
412
|
+
min-height: 100px;
|
|
411
413
|
}
|
|
412
414
|
|
|
413
415
|
.no-border {
|
|
@@ -50,19 +50,22 @@
|
|
|
50
50
|
| "error"
|
|
51
51
|
| "generating"
|
|
52
52
|
| "streaming" = "complete";
|
|
53
|
-
export let canvas_size: [number, number]
|
|
53
|
+
export let canvas_size: [number, number];
|
|
54
|
+
export let fixed_canvas = false;
|
|
54
55
|
export let realtime: boolean;
|
|
55
56
|
export let upload: Client["upload"];
|
|
56
57
|
export let stream_handler: Client["stream"];
|
|
57
58
|
export let dragging: boolean;
|
|
58
59
|
export let placeholder: string | undefined = undefined;
|
|
59
|
-
export let
|
|
60
|
+
export let dynamic_height: number | undefined = undefined;
|
|
61
|
+
export let height;
|
|
60
62
|
export let full_history: CommandNode | null = null;
|
|
61
63
|
|
|
62
64
|
const dispatch = createEventDispatcher<{
|
|
63
65
|
clear?: never;
|
|
64
66
|
upload?: never;
|
|
65
67
|
change?: never;
|
|
68
|
+
receive_null?: never;
|
|
66
69
|
}>();
|
|
67
70
|
|
|
68
71
|
let editor: ImageEditor;
|
|
@@ -78,7 +81,12 @@
|
|
|
78
81
|
$: if (bg) dispatch("upload");
|
|
79
82
|
|
|
80
83
|
export async function get_data(): Promise<ImageBlobs> {
|
|
81
|
-
|
|
84
|
+
let blobs;
|
|
85
|
+
try {
|
|
86
|
+
blobs = await editor.get_blobs();
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return { background: null, layers: [], composite: null };
|
|
89
|
+
}
|
|
82
90
|
|
|
83
91
|
const bg = blobs.background
|
|
84
92
|
? upload(
|
|
@@ -119,11 +127,11 @@
|
|
|
119
127
|
if (!editor) return;
|
|
120
128
|
if (value == null) {
|
|
121
129
|
editor.handle_remove();
|
|
130
|
+
dispatch("receive_null");
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
$: handle_value(value);
|
|
126
|
-
|
|
127
135
|
$: crop_constraint = crop_size;
|
|
128
136
|
let bg = false;
|
|
129
137
|
let history = false;
|
|
@@ -207,6 +215,10 @@
|
|
|
207
215
|
let active_mode: "webcam" | "color" | null = null;
|
|
208
216
|
let editor_height = height - 100;
|
|
209
217
|
|
|
218
|
+
let _dynamic_height: number;
|
|
219
|
+
|
|
220
|
+
$: dynamic_height = _dynamic_height;
|
|
221
|
+
|
|
210
222
|
$: [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
|
|
211
223
|
</script>
|
|
212
224
|
|
|
@@ -221,6 +233,7 @@
|
|
|
221
233
|
crop_size={Array.isArray(crop_size) ? crop_size : undefined}
|
|
222
234
|
bind:this={editor}
|
|
223
235
|
bind:height={editor_height}
|
|
236
|
+
bind:canvas_height={_dynamic_height}
|
|
224
237
|
parent_height={height}
|
|
225
238
|
{changeable}
|
|
226
239
|
on:save
|
|
@@ -242,10 +255,11 @@
|
|
|
242
255
|
{sources}
|
|
243
256
|
{upload}
|
|
244
257
|
{stream_handler}
|
|
258
|
+
{canvas_size}
|
|
245
259
|
bind:bg
|
|
246
260
|
bind:active_mode
|
|
247
261
|
background_file={value?.background || value?.composite || null}
|
|
248
|
-
|
|
262
|
+
{fixed_canvas}
|
|
249
263
|
></Sources>
|
|
250
264
|
|
|
251
265
|
{#if transforms.includes("crop")}
|
package/shared/tools/Crop.svelte
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
l_p = x_offset / $editor_box.child_width;
|
|
68
68
|
t_p = y_offset / $editor_box.child_height;
|
|
69
69
|
|
|
70
|
-
c = crop_canvas($pixi!.renderer, $pixi!.
|
|
70
|
+
c = crop_canvas($pixi!.renderer, $pixi!.background_container, crop, 0.2);
|
|
71
71
|
c.start(...$dimensions, current_crop, false);
|
|
72
72
|
|
|
73
73
|
c.continue(
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
|
|
112
112
|
cropper = crop_canvas(
|
|
113
113
|
$pixi?.renderer,
|
|
114
|
-
$pixi.
|
|
114
|
+
$pixi.background_container,
|
|
115
115
|
crop,
|
|
116
116
|
current_opacity
|
|
117
117
|
);
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
export let upload: Client["upload"];
|
|
29
29
|
export let stream_handler: Client["stream"];
|
|
30
30
|
export let dragging: boolean;
|
|
31
|
-
export let
|
|
31
|
+
export let canvas_size: [number, number];
|
|
32
|
+
export let fixed_canvas = false;
|
|
32
33
|
|
|
33
34
|
const { active_tool } = getContext<ToolContext>(TOOL_KEY);
|
|
34
35
|
const { pixi, dimensions, register_context, reset, editor_box } =
|
|
@@ -110,7 +111,8 @@
|
|
|
110
111
|
$pixi.renderer,
|
|
111
112
|
background,
|
|
112
113
|
$pixi.resize,
|
|
113
|
-
|
|
114
|
+
canvas_size,
|
|
115
|
+
fixed_canvas
|
|
114
116
|
);
|
|
115
117
|
$dimensions = await add_image.start();
|
|
116
118
|
|
|
@@ -187,8 +189,11 @@
|
|
|
187
189
|
class:click-disabled={!!bg ||
|
|
188
190
|
active_mode === "webcam" ||
|
|
189
191
|
$active_tool !== "bg"}
|
|
190
|
-
style:height="{
|
|
191
|
-
|
|
192
|
+
style:height="{Math.max(
|
|
193
|
+
$editor_box.child_height +
|
|
194
|
+
($editor_box.child_top - $editor_box.parent_top),
|
|
195
|
+
100
|
|
196
|
+
)}px"
|
|
192
197
|
>
|
|
193
198
|
<Upload
|
|
194
199
|
hidden={bg || active_mode === "webcam" || $active_tool !== "bg"}
|
package/shared/tools/crop.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface CropCommand extends Command {
|
|
|
26
26
|
|
|
27
27
|
export function crop_canvas(
|
|
28
28
|
renderer: IRenderer,
|
|
29
|
-
|
|
29
|
+
background_container: Container,
|
|
30
30
|
crop: Writable<[number, number, number, number]>,
|
|
31
31
|
current_opacity = 0
|
|
32
32
|
): CropCommand {
|
|
@@ -89,7 +89,7 @@ export function crop_canvas(
|
|
|
89
89
|
|
|
90
90
|
crop_mask(_width, _height, _previous_crop, _preview);
|
|
91
91
|
sprite = new Sprite(text);
|
|
92
|
-
|
|
92
|
+
background_container.mask = sprite;
|
|
93
93
|
width = _width;
|
|
94
94
|
height = _height;
|
|
95
95
|
if (set_previous)
|
|
@@ -131,16 +131,14 @@ export function crop_canvas(
|
|
|
131
131
|
clean = true;
|
|
132
132
|
} else {
|
|
133
133
|
if (!stopped) {
|
|
134
|
-
alpha_spring.set(0);
|
|
134
|
+
alpha_spring.set(0, { hard: true });
|
|
135
135
|
}
|
|
136
|
-
|
|
137
136
|
crop.set([
|
|
138
137
|
final_crop[0] / width,
|
|
139
138
|
final_crop[1] / height,
|
|
140
139
|
final_crop[2] / width,
|
|
141
140
|
final_crop[3] / height
|
|
142
141
|
]);
|
|
143
|
-
|
|
144
142
|
clean = true;
|
|
145
143
|
}
|
|
146
144
|
}
|
package/shared/tools/sources.ts
CHANGED
|
@@ -21,69 +21,113 @@ interface BgImageCommand extends Command {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param
|
|
26
|
-
* @param
|
|
27
|
-
* @param
|
|
28
|
-
* @param
|
|
29
|
-
* @returns
|
|
24
|
+
* Calculates new dimensions and position for an image to fit within a canvas while maintaining aspect ratio
|
|
25
|
+
* @param image_width Original width of the image
|
|
26
|
+
* @param image_height Original height of the image
|
|
27
|
+
* @param canvas_width Width of the canvas
|
|
28
|
+
* @param canvas_height Height of the canvas
|
|
29
|
+
* @returns Object containing new dimensions and position
|
|
30
30
|
*/
|
|
31
|
+
export function fit_image_to_canvas(
|
|
32
|
+
image_width: number,
|
|
33
|
+
image_height: number,
|
|
34
|
+
canvas_width: number,
|
|
35
|
+
canvas_height: number
|
|
36
|
+
): {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
} {
|
|
42
|
+
const image_aspect_ratio = image_width / image_height;
|
|
43
|
+
const canvas_aspect_ratio = canvas_width / canvas_height;
|
|
44
|
+
|
|
45
|
+
let new_width: number;
|
|
46
|
+
let new_height: number;
|
|
47
|
+
|
|
48
|
+
if (image_width <= canvas_width && image_height <= canvas_height) {
|
|
49
|
+
new_width = image_width;
|
|
50
|
+
new_height = image_height;
|
|
51
|
+
} else {
|
|
52
|
+
if (image_aspect_ratio > canvas_aspect_ratio) {
|
|
53
|
+
// Width is the limiting factor
|
|
54
|
+
new_width = canvas_width;
|
|
55
|
+
new_height = canvas_width / image_aspect_ratio;
|
|
56
|
+
} else {
|
|
57
|
+
// Height is the limiting factor
|
|
58
|
+
new_height = canvas_height;
|
|
59
|
+
new_width = canvas_height * image_aspect_ratio;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Calculate position to center the image
|
|
64
|
+
const x = Math.round((canvas_width - new_width) / 2);
|
|
65
|
+
const y = Math.round((canvas_height - new_height) / 2);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
width: Math.round(new_width),
|
|
69
|
+
height: Math.round(new_height),
|
|
70
|
+
x,
|
|
71
|
+
y
|
|
72
|
+
};
|
|
73
|
+
}
|
|
31
74
|
|
|
32
75
|
export function add_bg_image(
|
|
33
76
|
container: Container,
|
|
34
77
|
renderer: IRenderer,
|
|
35
78
|
background: Blob | File,
|
|
36
79
|
resize: (width: number, height: number) => void,
|
|
37
|
-
|
|
80
|
+
canvas_size: [number, number],
|
|
81
|
+
fixed_canvas: boolean
|
|
38
82
|
): BgImageCommand {
|
|
39
83
|
let sprite: Sprite & DisplayObject;
|
|
40
84
|
|
|
41
|
-
function calculate_dimensions(
|
|
42
|
-
width: number,
|
|
43
|
-
height: number,
|
|
44
|
-
max_height: number
|
|
45
|
-
): [number, number] {
|
|
46
|
-
const MAX_HEIGHT = max_height * 1.5;
|
|
47
|
-
const MAX_WIDTH = MAX_HEIGHT * 2;
|
|
48
|
-
|
|
49
|
-
let new_width = width;
|
|
50
|
-
let new_height = height;
|
|
51
|
-
|
|
52
|
-
if (height > MAX_HEIGHT) {
|
|
53
|
-
const ratio = MAX_HEIGHT / height;
|
|
54
|
-
new_width = width * ratio;
|
|
55
|
-
new_height = MAX_HEIGHT;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (new_width > MAX_WIDTH) {
|
|
59
|
-
const ratio = MAX_WIDTH / new_width;
|
|
60
|
-
new_width = MAX_WIDTH;
|
|
61
|
-
new_height = new_height * ratio;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return [Math.round(new_width), Math.round(new_height)];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
85
|
return {
|
|
68
86
|
async start() {
|
|
69
87
|
container.removeChildren();
|
|
70
88
|
const img = await createImageBitmap(background);
|
|
71
89
|
const bitmap_texture = Texture.from(img);
|
|
72
90
|
sprite = new Sprite(bitmap_texture) as Sprite & DisplayObject;
|
|
91
|
+
if (!fixed_canvas) {
|
|
92
|
+
canvas_size = [sprite.width, sprite.height];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (fixed_canvas) {
|
|
96
|
+
const [canvas_width, canvas_height] = canvas_size;
|
|
97
|
+
const { width, height, x, y } = fit_image_to_canvas(
|
|
98
|
+
sprite.width,
|
|
99
|
+
sprite.height,
|
|
100
|
+
canvas_width,
|
|
101
|
+
canvas_height
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
sprite.width = width;
|
|
105
|
+
sprite.height = height;
|
|
106
|
+
sprite.x = x;
|
|
107
|
+
sprite.y = y;
|
|
73
108
|
|
|
74
|
-
|
|
109
|
+
return canvas_size;
|
|
110
|
+
}
|
|
111
|
+
// Use existing max_height based scaling if no canvas_size
|
|
112
|
+
const x = fit_image_to_canvas(
|
|
75
113
|
sprite.width,
|
|
76
114
|
sprite.height,
|
|
77
|
-
|
|
115
|
+
canvas_size[0],
|
|
116
|
+
canvas_size[1]
|
|
78
117
|
);
|
|
79
|
-
sprite.width =
|
|
80
|
-
sprite.height =
|
|
81
|
-
|
|
82
|
-
|
|
118
|
+
sprite.width = x.width;
|
|
119
|
+
sprite.height = x.height;
|
|
120
|
+
sprite.x = 0;
|
|
121
|
+
sprite.y = 0;
|
|
122
|
+
return [x.width, x.height];
|
|
83
123
|
},
|
|
84
124
|
async execute() {
|
|
85
|
-
|
|
86
|
-
|
|
125
|
+
resize(
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
fixed_canvas ? canvas_size[0] : sprite.width,
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
fixed_canvas ? canvas_size[1] : sprite.height
|
|
130
|
+
);
|
|
87
131
|
|
|
88
132
|
sprite.zIndex = 0;
|
|
89
133
|
container.addChild(sprite);
|