@gradio/imageeditor 0.14.5 → 0.15.1
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/InteractiveImageEditor.svelte +4 -7
- package/dist/InteractiveImageEditor.svelte +4 -3
- package/dist/InteractiveImageEditor.svelte.d.ts +1 -1
- package/dist/shared/Controls.svelte +32 -1
- package/dist/shared/Controls.svelte.d.ts +2 -0
- package/dist/shared/ImageEditor.svelte +23 -1
- package/dist/shared/ImageEditor.svelte.d.ts +2 -1
- package/dist/shared/brush/brush-cursor.js +0 -42
- package/dist/shared/brush/brush-textures.d.ts +19 -2
- package/dist/shared/brush/brush-textures.js +120 -82
- package/dist/shared/brush/brush.js +0 -52
- package/dist/shared/{utils → core}/commands.d.ts +19 -58
- package/dist/shared/core/commands.js +70 -0
- package/dist/shared/core/editor.d.ts +4 -91
- package/dist/shared/core/editor.js +18 -708
- package/dist/shared/core/layers.d.ts +143 -0
- package/dist/shared/core/layers.js +774 -0
- package/dist/shared/image/image.d.ts +14 -4
- package/dist/shared/image/image.js +91 -18
- package/dist/shared/resize/resize.d.ts +31 -0
- package/dist/shared/resize/resize.js +127 -290
- package/dist/shared/zoom/zoom.js +0 -51
- package/package.json +7 -7
- package/shared/Controls.svelte +33 -1
- package/shared/ImageEditor.svelte +25 -9
- package/shared/Toolbar.svelte +1 -4
- package/shared/brush/ColorSwatch.svelte +0 -3
- package/shared/brush/brush-cursor.ts +0 -42
- package/shared/brush/brush-textures.ts +166 -83
- package/shared/brush/brush.ts +0 -53
- package/shared/core/commands.ts +118 -0
- package/shared/core/editor.ts +33 -957
- package/shared/core/layers.ts +1068 -0
- package/shared/image/image.ts +139 -21
- package/shared/resize/resize.ts +160 -299
- package/shared/zoom/zoom.ts +0 -51
- package/dist/shared/utils/commands.js +0 -73
- package/shared/utils/commands.ts +0 -174
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @gradio/imageeditor
|
|
2
2
|
|
|
3
|
+
## 0.15.1
|
|
4
|
+
|
|
5
|
+
### Dependency updates
|
|
6
|
+
|
|
7
|
+
- @gradio/statustracker@0.10.12
|
|
8
|
+
- @gradio/client@1.15.1
|
|
9
|
+
- @gradio/upload@0.16.6
|
|
10
|
+
- @gradio/image@0.22.7
|
|
11
|
+
|
|
12
|
+
## 0.15.0
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- [#11192](https://github.com/gradio-app/gradio/pull/11192) [`a03736f`](https://github.com/gradio-app/gradio/commit/a03736f97c642b338aa701166d3a0b299a46d27a) - Add undo and redo to the `ImageEditor` component. Thanks @pngwn!
|
|
17
|
+
|
|
18
|
+
### Dependency updates
|
|
19
|
+
|
|
20
|
+
- @gradio/statustracker@0.10.11
|
|
21
|
+
- @gradio/atoms@0.16.1
|
|
22
|
+
- @gradio/client@1.15.0
|
|
23
|
+
- @gradio/upload@0.16.5
|
|
24
|
+
- @gradio/image@0.22.6
|
|
25
|
+
|
|
3
26
|
## 0.14.5
|
|
4
27
|
|
|
5
28
|
### Dependency updates
|
|
@@ -16,18 +16,14 @@
|
|
|
16
16
|
import { createEventDispatcher } from "svelte";
|
|
17
17
|
import { type I18nFormatter } from "@gradio/utils";
|
|
18
18
|
import { prepare_files, type FileData, type Client } from "@gradio/client";
|
|
19
|
-
import { type CommandNode } from "./shared/
|
|
19
|
+
import { type CommandNode } from "./shared/core/commands";
|
|
20
20
|
import ImageEditor from "./shared/ImageEditor.svelte";
|
|
21
|
-
// import Layers from "./layers/Layers.svelte";
|
|
22
21
|
import { type Brush as IBrush, type Eraser } from "./shared/brush/types";
|
|
23
|
-
// import { type Eraser } from "./tools/Brush.svelte";
|
|
24
22
|
import { type Tool as ToolbarTool } from "./shared/Toolbar.svelte";
|
|
25
23
|
|
|
26
|
-
// import { Tools, Crop, Brush, Sources } from "./tools";
|
|
27
24
|
import { BlockLabel } from "@gradio/atoms";
|
|
28
25
|
import { Image as ImageIcon } from "@gradio/icons";
|
|
29
26
|
import { inject } from "./shared/utils/parse_placeholder";
|
|
30
|
-
// import Sources from "./shared/image/Sources.svelte";
|
|
31
27
|
import {
|
|
32
28
|
type LayerOptions,
|
|
33
29
|
type Transform,
|
|
@@ -138,7 +134,7 @@
|
|
|
138
134
|
$: handle_value({ layers, composite, background });
|
|
139
135
|
|
|
140
136
|
let background_image = false;
|
|
141
|
-
let
|
|
137
|
+
let can_undo: boolean;
|
|
142
138
|
|
|
143
139
|
export let image_id: null | string = null;
|
|
144
140
|
|
|
@@ -243,8 +239,9 @@
|
|
|
243
239
|
{webcam_options}
|
|
244
240
|
{show_download_button}
|
|
245
241
|
{theme_mode}
|
|
242
|
+
bind:can_undo
|
|
246
243
|
>
|
|
247
|
-
{#if
|
|
244
|
+
{#if current_tool === "image" && !can_undo}
|
|
248
245
|
<div class="empty wrap">
|
|
249
246
|
{#if sources && sources.length}
|
|
250
247
|
{#if heading || paragraph}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<script>import { createEventDispatcher } from "svelte";
|
|
4
4
|
import {} from "@gradio/utils";
|
|
5
5
|
import { prepare_files } from "@gradio/client";
|
|
6
|
-
import {} from "./shared/
|
|
6
|
+
import {} from "./shared/core/commands";
|
|
7
7
|
import ImageEditor from "./shared/ImageEditor.svelte";
|
|
8
8
|
import {} from "./shared/brush/types";
|
|
9
9
|
import {} from "./shared/Toolbar.svelte";
|
|
@@ -89,7 +89,7 @@ function handle_value(value) {
|
|
|
89
89
|
$:
|
|
90
90
|
handle_value({ layers, composite, background });
|
|
91
91
|
let background_image = false;
|
|
92
|
-
let
|
|
92
|
+
let can_undo;
|
|
93
93
|
export let image_id = null;
|
|
94
94
|
function nextframe() {
|
|
95
95
|
return new Promise((resolve) => setTimeout(() => resolve(), 30));
|
|
@@ -189,8 +189,9 @@ let current_tool;
|
|
|
189
189
|
{webcam_options}
|
|
190
190
|
{show_download_button}
|
|
191
191
|
{theme_mode}
|
|
192
|
+
bind:can_undo
|
|
192
193
|
>
|
|
193
|
-
{#if
|
|
194
|
+
{#if current_tool === "image" && !can_undo}
|
|
194
195
|
<div class="empty wrap">
|
|
195
196
|
{#if sources && sources.length}
|
|
196
197
|
{#if heading || paragraph}
|
|
@@ -11,7 +11,7 @@ export interface ImageBlobs {
|
|
|
11
11
|
}
|
|
12
12
|
import { type I18nFormatter } from "@gradio/utils";
|
|
13
13
|
import { type FileData, type Client } from "@gradio/client";
|
|
14
|
-
import { type CommandNode } from "./shared/
|
|
14
|
+
import { type CommandNode } from "./shared/core/commands";
|
|
15
15
|
import { type Brush as IBrush, type Eraser } from "./shared/brush/types";
|
|
16
16
|
import { type LayerOptions, type Transform, type Source, type WebcamOptions } from "./shared/types";
|
|
17
17
|
declare const __propDef: {
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
<script>import { createEventDispatcher } from "svelte";
|
|
2
2
|
import { IconButton, IconButtonWrapper } from "@gradio/atoms";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Check,
|
|
5
|
+
Trash,
|
|
6
|
+
ZoomIn,
|
|
7
|
+
ZoomOut,
|
|
8
|
+
Pan,
|
|
9
|
+
Download,
|
|
10
|
+
Undo,
|
|
11
|
+
Redo
|
|
12
|
+
} from "@gradio/icons";
|
|
4
13
|
export let can_save = false;
|
|
5
14
|
export let changeable = false;
|
|
6
15
|
export let current_zoom = 1;
|
|
7
16
|
export let tool;
|
|
8
17
|
export let min_zoom = true;
|
|
9
18
|
export let enable_download = false;
|
|
19
|
+
export let can_undo;
|
|
20
|
+
export let can_redo;
|
|
10
21
|
const dispatch = createEventDispatcher();
|
|
11
22
|
let show_zoom_popup = false;
|
|
12
23
|
function handle_zoom_click(e) {
|
|
@@ -97,6 +108,26 @@ $:
|
|
|
97
108
|
</div>
|
|
98
109
|
<div class="separator"></div>
|
|
99
110
|
|
|
111
|
+
<IconButton
|
|
112
|
+
Icon={Undo}
|
|
113
|
+
label="Undo"
|
|
114
|
+
on:click={(event) => {
|
|
115
|
+
dispatch("undo");
|
|
116
|
+
event.stopPropagation();
|
|
117
|
+
}}
|
|
118
|
+
disabled={!can_undo}
|
|
119
|
+
/>
|
|
120
|
+
|
|
121
|
+
<IconButton
|
|
122
|
+
Icon={Redo}
|
|
123
|
+
label="Redo"
|
|
124
|
+
on:click={(event) => {
|
|
125
|
+
dispatch("redo");
|
|
126
|
+
event.stopPropagation();
|
|
127
|
+
}}
|
|
128
|
+
disabled={!can_redo}
|
|
129
|
+
/>
|
|
130
|
+
|
|
100
131
|
{#if changeable}
|
|
101
132
|
<IconButton
|
|
102
133
|
disabled={!can_save}
|
|
@@ -8,7 +8,7 @@ import { ResizeTool } from "./resize/resize";
|
|
|
8
8
|
import { Webcam } from "@gradio/image";
|
|
9
9
|
import tinycolor, {} from "tinycolor2";
|
|
10
10
|
import { ZoomTool } from "./zoom/zoom";
|
|
11
|
-
import {} from "./
|
|
11
|
+
import {} from "./core/commands";
|
|
12
12
|
import { ImageEditor } from "./core/editor";
|
|
13
13
|
import {} from "./brush/types";
|
|
14
14
|
import { BrushTool } from "./brush/brush";
|
|
@@ -163,6 +163,8 @@ onMount(() => {
|
|
|
163
163
|
});
|
|
164
164
|
let crop;
|
|
165
165
|
let crop_zoom;
|
|
166
|
+
export let can_undo = false;
|
|
167
|
+
let can_redo = false;
|
|
166
168
|
async function init_image_editor() {
|
|
167
169
|
brush = new BrushTool();
|
|
168
170
|
zoom = new ZoomTool();
|
|
@@ -211,6 +213,8 @@ async function init_image_editor() {
|
|
|
211
213
|
});
|
|
212
214
|
editor.on("change", () => {
|
|
213
215
|
dispatch("change");
|
|
216
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
217
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
214
218
|
});
|
|
215
219
|
if (background || layers.length > 0) {
|
|
216
220
|
if (background) {
|
|
@@ -248,7 +252,11 @@ async function handle_files(files) {
|
|
|
248
252
|
dispatch("upload");
|
|
249
253
|
dispatch("input");
|
|
250
254
|
dispatch("change");
|
|
255
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
256
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
251
257
|
}
|
|
258
|
+
$:
|
|
259
|
+
background_image = can_undo && editor.command_manager.contains("AddImage");
|
|
252
260
|
function handle_tool_change({ tool }) {
|
|
253
261
|
editor.set_tool(tool);
|
|
254
262
|
current_tool = tool;
|
|
@@ -408,6 +416,16 @@ async function handle_download() {
|
|
|
408
416
|
link.click();
|
|
409
417
|
URL.revokeObjectURL(url);
|
|
410
418
|
}
|
|
419
|
+
function handle_undo() {
|
|
420
|
+
editor.undo();
|
|
421
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
422
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
423
|
+
}
|
|
424
|
+
function handle_redo() {
|
|
425
|
+
editor.redo();
|
|
426
|
+
can_undo = editor.command_manager.history.previous !== null;
|
|
427
|
+
can_redo = editor.command_manager.history.next !== null;
|
|
428
|
+
}
|
|
411
429
|
</script>
|
|
412
430
|
|
|
413
431
|
<div
|
|
@@ -447,6 +465,10 @@ async function handle_download() {
|
|
|
447
465
|
}}
|
|
448
466
|
enable_download={show_download_button}
|
|
449
467
|
on:download={() => handle_download()}
|
|
468
|
+
{can_undo}
|
|
469
|
+
{can_redo}
|
|
470
|
+
on:undo={handle_undo}
|
|
471
|
+
on:redo={handle_redo}
|
|
450
472
|
/>
|
|
451
473
|
{/if}
|
|
452
474
|
|
|
@@ -4,7 +4,7 @@ export type context_type = "bg" | "layers" | "crop" | "draw" | "erase";
|
|
|
4
4
|
import { type Tool as ToolbarTool } from "./Toolbar.svelte";
|
|
5
5
|
import type { I18nFormatter } from "@gradio/utils";
|
|
6
6
|
import type { Client, FileData } from "@gradio/client";
|
|
7
|
-
import { type CommandNode } from "./
|
|
7
|
+
import { type CommandNode } from "./core/commands";
|
|
8
8
|
import { type Brush, type Eraser } from "./brush/types";
|
|
9
9
|
import type { LayerOptions, Source, Transform, WebcamOptions } from "./types";
|
|
10
10
|
import { type ImageBlobs } from "./types";
|
|
@@ -43,6 +43,7 @@ declare const __propDef: {
|
|
|
43
43
|
};
|
|
44
44
|
} | any) => Promise<void>) | undefined;
|
|
45
45
|
add_layers_from_url?: ((source: FileData[] | any) => Promise<void>) | undefined;
|
|
46
|
+
can_undo?: boolean | undefined;
|
|
46
47
|
};
|
|
47
48
|
events: {
|
|
48
49
|
error: CustomEvent<string>;
|
|
@@ -10,7 +10,6 @@ export class BrushCursor {
|
|
|
10
10
|
image_editor_context;
|
|
11
11
|
state;
|
|
12
12
|
scale;
|
|
13
|
-
// Cursor-related properties
|
|
14
13
|
cursor_graphics = null;
|
|
15
14
|
cursor_container = null;
|
|
16
15
|
brush_preview_container = null;
|
|
@@ -19,7 +18,6 @@ export class BrushCursor {
|
|
|
19
18
|
is_cursor_over_image = false;
|
|
20
19
|
cursor_position_check_timeout = null;
|
|
21
20
|
is_brush_or_erase_active = false;
|
|
22
|
-
// Event handlers
|
|
23
21
|
_bound_update_cursor = null;
|
|
24
22
|
_bound_check_cursor_over_image = null;
|
|
25
23
|
constructor(image_editor_context, state, scale) {
|
|
@@ -39,18 +37,13 @@ export class BrushCursor {
|
|
|
39
37
|
* Sets up cursor-related event listeners.
|
|
40
38
|
*/
|
|
41
39
|
setup_event_listeners() {
|
|
42
|
-
// Clean up any existing listeners
|
|
43
40
|
this.cleanup_event_listeners();
|
|
44
|
-
// Create bound event handlers
|
|
45
41
|
this._bound_update_cursor = this.update_cursor_position.bind(this);
|
|
46
42
|
this._bound_check_cursor_over_image =
|
|
47
43
|
this.check_cursor_over_image.bind(this);
|
|
48
|
-
// Add event listeners using Pixi event system
|
|
49
|
-
// Use app.stage for global events like pointermove
|
|
50
44
|
const stage = this.image_editor_context.app.stage;
|
|
51
45
|
stage.on("pointermove", this._bound_update_cursor);
|
|
52
46
|
stage.on("pointermove", this._bound_check_cursor_over_image);
|
|
53
|
-
// Add event listeners to the image container for enter/leave events
|
|
54
47
|
this.image_editor_context.image_container.on("pointerenter", this.on_image_container_pointer_enter.bind(this));
|
|
55
48
|
this.image_editor_context.image_container.on("pointerleave", this.on_image_container_pointer_leave.bind(this));
|
|
56
49
|
}
|
|
@@ -58,9 +51,7 @@ export class BrushCursor {
|
|
|
58
51
|
* Removes all cursor-related event listeners.
|
|
59
52
|
*/
|
|
60
53
|
cleanup_event_listeners() {
|
|
61
|
-
// Use Pixi event system for removal
|
|
62
54
|
const stage = this.image_editor_context.app.stage;
|
|
63
|
-
// Remove event listeners if they exist
|
|
64
55
|
if (this._bound_update_cursor) {
|
|
65
56
|
stage.off("pointermove", this._bound_update_cursor);
|
|
66
57
|
this._bound_update_cursor = null;
|
|
@@ -69,17 +60,14 @@ export class BrushCursor {
|
|
|
69
60
|
stage.off("pointermove", this._bound_check_cursor_over_image);
|
|
70
61
|
this._bound_check_cursor_over_image = null;
|
|
71
62
|
}
|
|
72
|
-
// Remove image container event listeners
|
|
73
63
|
this.image_editor_context.image_container.off("pointerenter");
|
|
74
64
|
this.image_editor_context.image_container.off("pointerleave");
|
|
75
|
-
// Clear timeout
|
|
76
65
|
this.cursor_position_check_timeout = clear_timeout(this.cursor_position_check_timeout);
|
|
77
66
|
}
|
|
78
67
|
/**
|
|
79
68
|
* initializes the cursor for the brush tool.
|
|
80
69
|
*/
|
|
81
70
|
initialize_cursor() {
|
|
82
|
-
// clean up existing cursor if it exists
|
|
83
71
|
if (this.cursor_container) {
|
|
84
72
|
if (this.cursor_container.parent) {
|
|
85
73
|
this.cursor_container.parent.removeChild(this.cursor_container);
|
|
@@ -87,13 +75,10 @@ export class BrushCursor {
|
|
|
87
75
|
this.cursor_container.destroy({ children: true });
|
|
88
76
|
this.cursor_container = null;
|
|
89
77
|
}
|
|
90
|
-
// create new cursor container and add to ui container
|
|
91
78
|
this.cursor_container = new Container();
|
|
92
79
|
this.image_editor_context.ui_container.addChild(this.cursor_container);
|
|
93
|
-
// create cursor graphics
|
|
94
80
|
this.cursor_graphics = new Graphics();
|
|
95
81
|
this.cursor_container.addChild(this.cursor_graphics);
|
|
96
|
-
// draw initial cursor
|
|
97
82
|
this.update_cursor_appearance();
|
|
98
83
|
}
|
|
99
84
|
/**
|
|
@@ -102,13 +87,10 @@ export class BrushCursor {
|
|
|
102
87
|
update_cursor_appearance() {
|
|
103
88
|
if (!this.cursor_graphics)
|
|
104
89
|
return;
|
|
105
|
-
// clear previous graphics
|
|
106
90
|
this.cursor_graphics.clear();
|
|
107
|
-
// set color based on mode
|
|
108
91
|
const cursor_color = this.state.mode === "draw"
|
|
109
92
|
? tinycolor(this.state.color).toString()
|
|
110
93
|
: 0xffffff;
|
|
111
|
-
// draw circle with current brush size
|
|
112
94
|
this.cursor_graphics
|
|
113
95
|
.circle(0, 0, this.state.brush_size * this.scale)
|
|
114
96
|
.stroke({
|
|
@@ -116,7 +98,6 @@ export class BrushCursor {
|
|
|
116
98
|
color: cursor_color,
|
|
117
99
|
alpha: 0.8
|
|
118
100
|
});
|
|
119
|
-
// add a small dot in the center for precision
|
|
120
101
|
this.cursor_graphics.circle(0, 0, 1).fill({
|
|
121
102
|
color: cursor_color,
|
|
122
103
|
alpha: 0.8
|
|
@@ -129,11 +110,8 @@ export class BrushCursor {
|
|
|
129
110
|
update_cursor_position(event) {
|
|
130
111
|
if (!this.cursor_container)
|
|
131
112
|
return;
|
|
132
|
-
// get position in image coordinates
|
|
133
113
|
const local_pos = this.image_editor_context.image_container.toLocal(event.global);
|
|
134
|
-
// convert to ui coordinates
|
|
135
114
|
const ui_pos = this.image_editor_context.ui_container.toLocal(this.image_editor_context.image_container.toGlobal(local_pos));
|
|
136
|
-
// update cursor position
|
|
137
115
|
this.cursor_container.position.set(ui_pos.x, ui_pos.y);
|
|
138
116
|
}
|
|
139
117
|
/**
|
|
@@ -141,22 +119,18 @@ export class BrushCursor {
|
|
|
141
119
|
* @param {FederatedPointerEvent} event - The pointer event.
|
|
142
120
|
*/
|
|
143
121
|
check_cursor_over_image(event) {
|
|
144
|
-
// cancel any pending timeout
|
|
145
122
|
if (this.cursor_position_check_timeout !== null) {
|
|
146
123
|
window.clearTimeout(this.cursor_position_check_timeout);
|
|
147
124
|
this.cursor_position_check_timeout = null;
|
|
148
125
|
}
|
|
149
|
-
// get the image container bounds in global coordinates
|
|
150
126
|
const image_container = this.image_editor_context.image_container;
|
|
151
127
|
const bounds = image_container.getBounds();
|
|
152
|
-
// check if cursor is over image
|
|
153
128
|
const was_over_image = this.is_cursor_over_image;
|
|
154
129
|
this.is_cursor_over_image =
|
|
155
130
|
event.global.x >= bounds.x &&
|
|
156
131
|
event.global.x <= bounds.x + bounds.width &&
|
|
157
132
|
event.global.y >= bounds.y &&
|
|
158
133
|
event.global.y <= bounds.y + bounds.height;
|
|
159
|
-
// update visibility if state changed
|
|
160
134
|
if (was_over_image !== this.is_cursor_over_image) {
|
|
161
135
|
this.update_cursor_and_preview_visibility();
|
|
162
136
|
}
|
|
@@ -194,7 +168,6 @@ export class BrushCursor {
|
|
|
194
168
|
show && this.is_brush_or_erase_active;
|
|
195
169
|
}
|
|
196
170
|
if (show) {
|
|
197
|
-
// update preview appearance and position
|
|
198
171
|
this.update_brush_preview();
|
|
199
172
|
this.update_brush_preview_position();
|
|
200
173
|
}
|
|
@@ -203,7 +176,6 @@ export class BrushCursor {
|
|
|
203
176
|
* initializes the brush preview.
|
|
204
177
|
*/
|
|
205
178
|
initialize_brush_preview() {
|
|
206
|
-
// clean up existing preview if it exists
|
|
207
179
|
if (this.brush_preview_container) {
|
|
208
180
|
if (this.brush_preview_container.parent) {
|
|
209
181
|
this.brush_preview_container.parent.removeChild(this.brush_preview_container);
|
|
@@ -211,13 +183,10 @@ export class BrushCursor {
|
|
|
211
183
|
this.brush_preview_container.destroy({ children: true });
|
|
212
184
|
this.brush_preview_container = null;
|
|
213
185
|
}
|
|
214
|
-
// create new preview container and add to ui container
|
|
215
186
|
this.brush_preview_container = new Container();
|
|
216
187
|
this.image_editor_context.ui_container.addChild(this.brush_preview_container);
|
|
217
|
-
// create preview graphics
|
|
218
188
|
this.brush_preview_graphics = new Graphics();
|
|
219
189
|
this.brush_preview_container.addChild(this.brush_preview_graphics);
|
|
220
|
-
// set initial visibility
|
|
221
190
|
this.brush_preview_container.visible = false;
|
|
222
191
|
}
|
|
223
192
|
/**
|
|
@@ -226,19 +195,15 @@ export class BrushCursor {
|
|
|
226
195
|
update_brush_preview() {
|
|
227
196
|
if (!this.brush_preview_graphics)
|
|
228
197
|
return;
|
|
229
|
-
// clear previous graphics
|
|
230
198
|
this.brush_preview_graphics.clear();
|
|
231
|
-
// draw brush preview based on current settings
|
|
232
199
|
const preview_size = this.state.brush_size * this.scale;
|
|
233
200
|
const preview_color = this.state.mode === "draw"
|
|
234
201
|
? tinycolor(this.state.color).setAlpha(this.state.opacity).toString()
|
|
235
202
|
: 0xffffff;
|
|
236
|
-
// draw circle with current brush settings
|
|
237
203
|
this.brush_preview_graphics.circle(0, 0, preview_size).fill({
|
|
238
204
|
color: preview_color,
|
|
239
205
|
alpha: this.state.mode === "draw" ? this.state.opacity : 0.3
|
|
240
206
|
});
|
|
241
|
-
// add outline
|
|
242
207
|
this.brush_preview_graphics.circle(0, 0, preview_size + 1).stroke({
|
|
243
208
|
width: 1,
|
|
244
209
|
color: 0x000000,
|
|
@@ -251,14 +216,11 @@ export class BrushCursor {
|
|
|
251
216
|
update_brush_preview_position() {
|
|
252
217
|
if (!this.brush_preview_container)
|
|
253
218
|
return;
|
|
254
|
-
// get the center of the image in global coordinates
|
|
255
219
|
const image_container = this.image_editor_context.image_container;
|
|
256
220
|
const center_x = image_container.width / 2;
|
|
257
221
|
const center_y = image_container.height / 2;
|
|
258
222
|
const global_pos = image_container.toGlobal({ x: center_x, y: center_y });
|
|
259
|
-
// convert to ui coordinates
|
|
260
223
|
const ui_pos = this.image_editor_context.ui_container.toLocal(global_pos);
|
|
261
|
-
// update preview position
|
|
262
224
|
this.brush_preview_container.position.set(ui_pos.x, ui_pos.y);
|
|
263
225
|
}
|
|
264
226
|
/**
|
|
@@ -289,9 +251,7 @@ export class BrushCursor {
|
|
|
289
251
|
* Cleans up all cursor resources.
|
|
290
252
|
*/
|
|
291
253
|
cleanup() {
|
|
292
|
-
// Reset active state
|
|
293
254
|
this.is_brush_or_erase_active = false;
|
|
294
|
-
// clean up cursor
|
|
295
255
|
if (this.cursor_container) {
|
|
296
256
|
if (this.cursor_container.parent) {
|
|
297
257
|
this.cursor_container.parent.removeChild(this.cursor_container);
|
|
@@ -299,7 +259,6 @@ export class BrushCursor {
|
|
|
299
259
|
this.cursor_container.destroy({ children: true });
|
|
300
260
|
this.cursor_container = null;
|
|
301
261
|
}
|
|
302
|
-
// clean up preview
|
|
303
262
|
if (this.brush_preview_container) {
|
|
304
263
|
if (this.brush_preview_container.parent) {
|
|
305
264
|
this.brush_preview_container.parent.removeChild(this.brush_preview_container);
|
|
@@ -307,7 +266,6 @@ export class BrushCursor {
|
|
|
307
266
|
this.brush_preview_container.destroy({ children: true });
|
|
308
267
|
this.brush_preview_container = null;
|
|
309
268
|
}
|
|
310
|
-
// clear timeout
|
|
311
269
|
if (this.cursor_position_check_timeout !== null) {
|
|
312
270
|
window.clearTimeout(this.cursor_position_check_timeout);
|
|
313
271
|
this.cursor_position_check_timeout = null;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
import { Application } from "pixi.js";
|
|
1
|
+
import { Application, Texture } from "pixi.js";
|
|
2
2
|
import { type ImageEditorContext } from "../core/editor";
|
|
3
|
+
import { type Command } from "../core/commands";
|
|
4
|
+
export declare class BrushCommand implements Command {
|
|
5
|
+
private context;
|
|
6
|
+
private layer_id;
|
|
7
|
+
private original_texture;
|
|
8
|
+
private final_texture;
|
|
9
|
+
name: string;
|
|
10
|
+
constructor(context: ImageEditorContext, layer_id: string, original_texture: Texture, final_texture: Texture);
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new texture with the same content as the source texture
|
|
13
|
+
*/
|
|
14
|
+
private createTextureFrom;
|
|
15
|
+
execute(): Promise<void>;
|
|
16
|
+
undo(): Promise<void>;
|
|
17
|
+
}
|
|
3
18
|
/**
|
|
4
19
|
* Class to handle texture operations for the brush tool.
|
|
5
20
|
* Simplified to work directly with layer textures.
|
|
@@ -18,6 +33,8 @@ export declare class BrushTextures {
|
|
|
18
33
|
private is_new_stroke;
|
|
19
34
|
private current_opacity;
|
|
20
35
|
private current_mode;
|
|
36
|
+
private original_layer_texture;
|
|
37
|
+
private active_layer_id;
|
|
21
38
|
constructor(image_editor_context: ImageEditorContext, app: Application);
|
|
22
39
|
/**
|
|
23
40
|
* Initializes textures needed for the brush tool.
|
|
@@ -40,7 +57,7 @@ export declare class BrushTextures {
|
|
|
40
57
|
*/
|
|
41
58
|
reset_eraser_mask(): void;
|
|
42
59
|
/**
|
|
43
|
-
* Commits the current stroke to the active layer.
|
|
60
|
+
* Commits the current stroke to the active layer and returns a command for undo/redo.
|
|
44
61
|
*/
|
|
45
62
|
commit_stroke(): void;
|
|
46
63
|
/**
|