@gradio/imageeditor 0.9.0 → 0.11.0-beta.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 +53 -0
- package/ImageEditor.stories.svelte +1 -0
- package/Index.svelte +7 -2
- package/dist/Example.svelte +41 -0
- package/dist/Example.svelte.d.ts +19 -0
- package/dist/Index.svelte +184 -0
- package/dist/Index.svelte.d.ts +192 -0
- package/dist/shared/Controls.svelte +73 -0
- package/dist/shared/Controls.svelte.d.ts +24 -0
- package/dist/shared/ImageEditor.svelte +329 -0
- package/dist/shared/ImageEditor.svelte.d.ts +79 -0
- package/dist/shared/InteractiveImageEditor.svelte +273 -0
- package/dist/shared/InteractiveImageEditor.svelte.d.ts +58 -0
- package/dist/shared/layers/Layers.svelte +216 -0
- package/dist/shared/layers/Layers.svelte.d.ts +18 -0
- package/dist/shared/layers/utils.d.ts +69 -0
- package/dist/shared/layers/utils.js +131 -0
- package/dist/shared/tools/Brush.svelte +208 -0
- package/dist/shared/tools/Brush.svelte.d.ts +29 -0
- package/dist/shared/tools/BrushOptions.svelte +162 -0
- package/dist/shared/tools/BrushOptions.svelte.d.ts +29 -0
- package/dist/shared/tools/BrushSize.svelte +60 -0
- package/dist/shared/tools/BrushSize.svelte.d.ts +20 -0
- package/dist/shared/tools/ColorField.svelte +159 -0
- package/dist/shared/tools/ColorField.svelte.d.ts +20 -0
- package/dist/shared/tools/ColorPicker.svelte +179 -0
- package/dist/shared/tools/ColorPicker.svelte.d.ts +16 -0
- package/dist/shared/tools/ColorSwatch.svelte +183 -0
- package/dist/shared/tools/ColorSwatch.svelte.d.ts +30 -0
- package/dist/shared/tools/Crop.svelte +155 -0
- package/dist/shared/tools/Crop.svelte.d.ts +16 -0
- package/dist/shared/tools/Cropper.svelte +271 -0
- package/dist/shared/tools/Cropper.svelte.d.ts +41 -0
- package/dist/shared/tools/Handle.svelte +260 -0
- package/dist/shared/tools/Handle.svelte.d.ts +29 -0
- package/dist/shared/tools/Sources.svelte +250 -0
- package/dist/shared/tools/Sources.svelte.d.ts +32 -0
- package/dist/shared/tools/Tools.svelte +101 -0
- package/dist/shared/tools/Tools.svelte.d.ts +48 -0
- package/dist/shared/tools/brush.d.ts +62 -0
- package/dist/shared/tools/brush.js +133 -0
- package/dist/shared/tools/crop.d.ts +15 -0
- package/dist/shared/tools/crop.js +154 -0
- package/dist/shared/tools/index.d.ts +5 -0
- package/dist/shared/tools/index.js +5 -0
- package/dist/shared/tools/sources.d.ts +40 -0
- package/dist/shared/tools/sources.js +69 -0
- package/dist/shared/tools/types.d.ts +1 -0
- package/dist/shared/tools/types.js +1 -0
- package/dist/shared/utils/commands.d.ts +96 -0
- package/dist/shared/utils/commands.js +65 -0
- package/dist/shared/utils/events.d.ts +7 -0
- package/dist/shared/utils/events.js +22 -0
- package/dist/shared/utils/parse_placeholder.d.ts +1 -0
- package/dist/shared/utils/parse_placeholder.js +19 -0
- package/dist/shared/utils/pixi.d.ts +76 -0
- package/dist/shared/utils/pixi.js +152 -0
- package/package.json +28 -12
- package/shared/ImageEditor.svelte +8 -4
- package/shared/InteractiveImageEditor.svelte +35 -6
- package/shared/tools/Brush.svelte +4 -6
- package/shared/tools/BrushOptions.svelte +1 -1
- package/shared/tools/Crop.svelte +2 -1
- package/shared/utils/parse_placeholder.ts +27 -0
- package/shared/utils/pixi.ts +0 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Container, type ColorSource, type IRenderer } from "pixi.js";
|
|
2
|
+
import type { LayerScene } from "../layers/utils";
|
|
3
|
+
import { type Command } from "../utils/commands";
|
|
4
|
+
export interface DrawCommand extends Command {
|
|
5
|
+
/**
|
|
6
|
+
* sets the initial state to begin drawing
|
|
7
|
+
* @param options the options for drawing
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
start: (options: DrawOptions) => void;
|
|
11
|
+
/**
|
|
12
|
+
* continues drawing, smoothly interpolating between points where necessary
|
|
13
|
+
* @param options the options for drawing
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
continue: (options: Points) => void;
|
|
17
|
+
/**
|
|
18
|
+
* stops drawing
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
21
|
+
stop: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Whether or not the user is currently drawing.
|
|
24
|
+
*/
|
|
25
|
+
drawing: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Coordinates for a draw or erase path.
|
|
29
|
+
*/
|
|
30
|
+
interface Points {
|
|
31
|
+
/**
|
|
32
|
+
* The x coordinate of the path.
|
|
33
|
+
*/
|
|
34
|
+
x: number;
|
|
35
|
+
/**
|
|
36
|
+
* The y coordinate of the path.
|
|
37
|
+
*/
|
|
38
|
+
y: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Options for drawing a path.
|
|
42
|
+
*/
|
|
43
|
+
interface DrawOptions extends Points {
|
|
44
|
+
/**
|
|
45
|
+
* The size of the brush.
|
|
46
|
+
*/
|
|
47
|
+
size: number;
|
|
48
|
+
/**
|
|
49
|
+
* The color of the brush.
|
|
50
|
+
*/
|
|
51
|
+
color?: ColorSource;
|
|
52
|
+
/**
|
|
53
|
+
* The opacity of the brush.
|
|
54
|
+
*/
|
|
55
|
+
opacity: number;
|
|
56
|
+
/**
|
|
57
|
+
* Whether or not to set the initial texture.
|
|
58
|
+
*/
|
|
59
|
+
set_initial_texture?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare function draw_path(renderer: IRenderer, stage: Container, layer: LayerScene, mode: "draw" | "erase"): DrawCommand;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Container, Graphics, Color, RenderTexture, Sprite } from "pixi.js";
|
|
2
|
+
import {} from "../utils/commands";
|
|
3
|
+
import { make_graphics } from "../utils/pixi";
|
|
4
|
+
/**
|
|
5
|
+
* Draws a circle on the given graphics object.
|
|
6
|
+
* @param graphics the graphics object to draw on
|
|
7
|
+
* @param x the x coordinate of the circle
|
|
8
|
+
* @param y the y coordinate of the circle
|
|
9
|
+
* @param brush_color the color of the circle
|
|
10
|
+
* @param brush_size the radius of the circle
|
|
11
|
+
*/
|
|
12
|
+
function drawCircle(graphics, x, y, brush_color = new Color("black"), brush_size) {
|
|
13
|
+
const color = new Color(brush_color);
|
|
14
|
+
graphics.beginFill(color);
|
|
15
|
+
graphics.drawCircle(x, y, brush_size);
|
|
16
|
+
graphics.endFill();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Interpolates between two points.
|
|
20
|
+
* @param point1 the first point
|
|
21
|
+
* @param point2 the second point
|
|
22
|
+
* @returns an array of points between the two points
|
|
23
|
+
*/
|
|
24
|
+
function interpolate(point1, point2) {
|
|
25
|
+
let points = [];
|
|
26
|
+
const dx = point2.x - point1.x;
|
|
27
|
+
const dy = point2.y - point1.y;
|
|
28
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
29
|
+
const steps = Math.ceil(distance / 2);
|
|
30
|
+
const stepX = dx / steps;
|
|
31
|
+
const stepY = dy / steps;
|
|
32
|
+
for (let j = 0; j < steps; j++) {
|
|
33
|
+
const x = point1.x + j * stepX;
|
|
34
|
+
const y = point1.y + j * stepY;
|
|
35
|
+
points.push({ x, y });
|
|
36
|
+
}
|
|
37
|
+
return points;
|
|
38
|
+
}
|
|
39
|
+
export function draw_path(renderer, stage, layer, mode) {
|
|
40
|
+
const paths = [];
|
|
41
|
+
let initial_path;
|
|
42
|
+
let graphics;
|
|
43
|
+
let InitialTexture;
|
|
44
|
+
let has_drawn = false;
|
|
45
|
+
let id = 0;
|
|
46
|
+
return {
|
|
47
|
+
drawing: false,
|
|
48
|
+
start: function ({ x, y, size, color = new Color("black"), opacity, set_initial_texture = true }) {
|
|
49
|
+
if (set_initial_texture) {
|
|
50
|
+
InitialTexture = RenderTexture.create({
|
|
51
|
+
width: layer.draw_texture.width,
|
|
52
|
+
height: layer.draw_texture.height
|
|
53
|
+
});
|
|
54
|
+
renderer.render(layer.composite, {
|
|
55
|
+
renderTexture: InitialTexture
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
initial_path = { x, y, size, color, opacity };
|
|
59
|
+
paths.push({ x, y });
|
|
60
|
+
graphics = make_graphics(id++);
|
|
61
|
+
drawCircle(graphics, x, y, color, size);
|
|
62
|
+
renderer.render(graphics, {
|
|
63
|
+
renderTexture: mode === "draw" ? layer.draw_texture : layer.erase_texture,
|
|
64
|
+
clear: false
|
|
65
|
+
});
|
|
66
|
+
this.drawing = true;
|
|
67
|
+
},
|
|
68
|
+
continue: function ({ x, y }) {
|
|
69
|
+
const last_point = paths[paths.length - 1];
|
|
70
|
+
const new_points = interpolate(last_point, { x, y });
|
|
71
|
+
for (let i = 0; i < new_points.length; i++) {
|
|
72
|
+
const { x, y } = new_points[i];
|
|
73
|
+
drawCircle(graphics, x, y, initial_path.color, initial_path.size);
|
|
74
|
+
paths.push({ x, y });
|
|
75
|
+
}
|
|
76
|
+
renderer.render(graphics, {
|
|
77
|
+
renderTexture: mode === "draw" ? layer.draw_texture : layer.erase_texture,
|
|
78
|
+
clear: false
|
|
79
|
+
});
|
|
80
|
+
graphics.clear();
|
|
81
|
+
},
|
|
82
|
+
stop: function () {
|
|
83
|
+
const current_sketch = RenderTexture.create({
|
|
84
|
+
width: layer.draw_texture.width,
|
|
85
|
+
height: layer.draw_texture.height
|
|
86
|
+
});
|
|
87
|
+
renderer.render(layer.composite, {
|
|
88
|
+
renderTexture: current_sketch
|
|
89
|
+
});
|
|
90
|
+
renderer.render(new Sprite(current_sketch), {
|
|
91
|
+
renderTexture: layer.draw_texture
|
|
92
|
+
});
|
|
93
|
+
const clear_graphics = new Graphics()
|
|
94
|
+
.beginFill(0x000000, 0) // Use a fill color with 0 alpha for transparency
|
|
95
|
+
.drawRect(0, 0, layer.erase_texture.width, layer.erase_texture.height)
|
|
96
|
+
.endFill();
|
|
97
|
+
renderer.render(clear_graphics, {
|
|
98
|
+
renderTexture: layer.erase_texture,
|
|
99
|
+
clear: true
|
|
100
|
+
});
|
|
101
|
+
has_drawn = true;
|
|
102
|
+
this.drawing = false;
|
|
103
|
+
},
|
|
104
|
+
execute: function () {
|
|
105
|
+
if (!has_drawn) {
|
|
106
|
+
for (let i = 1; i < paths.length; i++) {
|
|
107
|
+
const { x, y } = paths[i];
|
|
108
|
+
drawCircle(graphics, x, y, initial_path.color, initial_path.size);
|
|
109
|
+
}
|
|
110
|
+
renderer.render(graphics, {
|
|
111
|
+
renderTexture: mode === "draw" ? layer.draw_texture : layer.erase_texture,
|
|
112
|
+
clear: false
|
|
113
|
+
});
|
|
114
|
+
this.stop();
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
undo: function () {
|
|
118
|
+
const clear_graphics = new Graphics()
|
|
119
|
+
.beginFill(0x000000, 0)
|
|
120
|
+
.drawRect(0, 0, layer.erase_texture.width, layer.erase_texture.height)
|
|
121
|
+
.endFill();
|
|
122
|
+
renderer.render(new Sprite(InitialTexture), {
|
|
123
|
+
renderTexture: layer.draw_texture
|
|
124
|
+
});
|
|
125
|
+
renderer.render(clear_graphics, {
|
|
126
|
+
renderTexture: layer.erase_texture,
|
|
127
|
+
clear: true
|
|
128
|
+
});
|
|
129
|
+
this.stop();
|
|
130
|
+
has_drawn = false;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type IRenderer, type Container } from "pixi.js";
|
|
2
|
+
import { type Command } from "../utils/commands";
|
|
3
|
+
import type { Writable } from "svelte/store";
|
|
4
|
+
export interface CropCommand extends Command {
|
|
5
|
+
start: (width: number, height: number, previous_crop: [number, number, number, number], preview?: boolean, set_previous?: boolean) => void;
|
|
6
|
+
stop: () => number;
|
|
7
|
+
continue: (crop_size: [number, number, number, number], preview?: boolean) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function crop_canvas(renderer: IRenderer, mask_container: Container, crop: Writable<[number, number, number, number]>, current_opacity?: number): CropCommand;
|
|
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
|
+
new_width: number;
|
|
12
|
+
new_height: number;
|
|
13
|
+
x_offset: number;
|
|
14
|
+
y_offset: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Graphics, Sprite, RenderTexture } from "pixi.js";
|
|
2
|
+
import {} from "../utils/commands";
|
|
3
|
+
import { spring } from "svelte/motion";
|
|
4
|
+
export function crop_canvas(renderer, mask_container, crop, current_opacity = 0) {
|
|
5
|
+
let text;
|
|
6
|
+
let sprite;
|
|
7
|
+
const mask_graphics = new Graphics();
|
|
8
|
+
let previous_crop;
|
|
9
|
+
let final_crop;
|
|
10
|
+
let width;
|
|
11
|
+
let height;
|
|
12
|
+
let alpha_spring = spring(current_opacity, {
|
|
13
|
+
stiffness: 0.1,
|
|
14
|
+
damping: 0.5
|
|
15
|
+
});
|
|
16
|
+
let spring_value = current_opacity;
|
|
17
|
+
alpha_spring.subscribe((value) => {
|
|
18
|
+
if (!final_crop)
|
|
19
|
+
return;
|
|
20
|
+
spring_value = value;
|
|
21
|
+
crop_mask(width, height, final_crop, true);
|
|
22
|
+
});
|
|
23
|
+
function crop_mask(width, height, crop_size, preview) {
|
|
24
|
+
mask_graphics.clear();
|
|
25
|
+
if (preview) {
|
|
26
|
+
mask_graphics.beginFill(0xffffff, spring_value);
|
|
27
|
+
mask_graphics.drawRect(0, 0, width, height);
|
|
28
|
+
mask_graphics.endFill();
|
|
29
|
+
}
|
|
30
|
+
mask_graphics.beginFill(0xffffff, 1);
|
|
31
|
+
mask_graphics.drawRect(...crop_size);
|
|
32
|
+
mask_graphics.endFill();
|
|
33
|
+
renderer.render(mask_graphics, {
|
|
34
|
+
renderTexture: text
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
let clean = true;
|
|
38
|
+
let stopped = false;
|
|
39
|
+
return {
|
|
40
|
+
start: (_width, _height, _previous_crop, _preview = true, set_previous = true) => {
|
|
41
|
+
clean = false;
|
|
42
|
+
text = RenderTexture.create({
|
|
43
|
+
width: _width,
|
|
44
|
+
height: _height
|
|
45
|
+
});
|
|
46
|
+
crop_mask(_width, _height, _previous_crop, _preview);
|
|
47
|
+
sprite = new Sprite(text);
|
|
48
|
+
mask_container.mask = sprite;
|
|
49
|
+
width = _width;
|
|
50
|
+
height = _height;
|
|
51
|
+
if (set_previous)
|
|
52
|
+
previous_crop = JSON.parse(JSON.stringify(_previous_crop));
|
|
53
|
+
},
|
|
54
|
+
continue: (crop_size, preview = true) => {
|
|
55
|
+
final_crop = JSON.parse(JSON.stringify(crop_size));
|
|
56
|
+
if (spring_value === 0.2) {
|
|
57
|
+
crop_mask(width, height, final_crop, preview);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
alpha_spring.set(0.2);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
undo() {
|
|
64
|
+
this.start(width, height, previous_crop, false);
|
|
65
|
+
crop.set([
|
|
66
|
+
previous_crop[0] / width,
|
|
67
|
+
previous_crop[1] / height,
|
|
68
|
+
previous_crop[2] / width,
|
|
69
|
+
previous_crop[3] / height
|
|
70
|
+
]);
|
|
71
|
+
clean = true;
|
|
72
|
+
},
|
|
73
|
+
stop() {
|
|
74
|
+
stopped = true;
|
|
75
|
+
return spring_value;
|
|
76
|
+
},
|
|
77
|
+
execute() {
|
|
78
|
+
if (clean) {
|
|
79
|
+
this.start(width, height, final_crop, false);
|
|
80
|
+
crop.set([
|
|
81
|
+
final_crop[0] / width,
|
|
82
|
+
final_crop[1] / height,
|
|
83
|
+
final_crop[2] / width,
|
|
84
|
+
final_crop[3] / height
|
|
85
|
+
]);
|
|
86
|
+
clean = true;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
if (!stopped) {
|
|
90
|
+
alpha_spring.set(0);
|
|
91
|
+
}
|
|
92
|
+
crop.set([
|
|
93
|
+
final_crop[0] / width,
|
|
94
|
+
final_crop[1] / height,
|
|
95
|
+
final_crop[2] / width,
|
|
96
|
+
final_crop[3] / height
|
|
97
|
+
]);
|
|
98
|
+
clean = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function resize_and_reposition(original_width, original_height, anchor, aspect_ratio, max_width, max_height) {
|
|
104
|
+
let new_width = original_width;
|
|
105
|
+
let new_height = original_height;
|
|
106
|
+
// Calculate new dimensions based on aspect ratio
|
|
107
|
+
if (anchor.includes("t") || anchor.includes("b") || anchor == "c") {
|
|
108
|
+
new_width = original_height * aspect_ratio;
|
|
109
|
+
}
|
|
110
|
+
if (anchor.includes("l") || anchor.includes("r") || anchor == "c") {
|
|
111
|
+
new_height = original_width / aspect_ratio;
|
|
112
|
+
}
|
|
113
|
+
// Ensure aspect ratio is maintained
|
|
114
|
+
new_height = new_height || new_width / aspect_ratio;
|
|
115
|
+
new_width = new_width || new_height * aspect_ratio;
|
|
116
|
+
// Apply max width/height constraints and adjust dimensions
|
|
117
|
+
if (new_width > max_width) {
|
|
118
|
+
new_width = max_width;
|
|
119
|
+
new_height = new_width / aspect_ratio;
|
|
120
|
+
}
|
|
121
|
+
if (new_height > max_height) {
|
|
122
|
+
new_height = max_height;
|
|
123
|
+
new_width = new_height * aspect_ratio;
|
|
124
|
+
}
|
|
125
|
+
// Calculate offsets to ensure anchor position is maintained
|
|
126
|
+
let x_offset = 0;
|
|
127
|
+
let y_offset = 0;
|
|
128
|
+
if (anchor.includes("r")) {
|
|
129
|
+
x_offset = original_width - new_width;
|
|
130
|
+
}
|
|
131
|
+
else if (anchor.includes("l")) {
|
|
132
|
+
x_offset = 0;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Center or top/bottom center
|
|
136
|
+
x_offset = (original_width - new_width) / 2;
|
|
137
|
+
}
|
|
138
|
+
if (anchor.includes("b")) {
|
|
139
|
+
y_offset = original_height - new_height;
|
|
140
|
+
}
|
|
141
|
+
else if (anchor.includes("t")) {
|
|
142
|
+
y_offset = 0;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Center or left/right center
|
|
146
|
+
y_offset = (original_height - new_height) / 2;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
new_width: new_width,
|
|
150
|
+
new_height: new_height,
|
|
151
|
+
x_offset: x_offset,
|
|
152
|
+
y_offset: y_offset
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Container, type IRenderer, type ColorSource } from "pixi.js";
|
|
2
|
+
import { type Command } from "../utils/commands";
|
|
3
|
+
interface BgImageCommand extends Command {
|
|
4
|
+
/**
|
|
5
|
+
* Initial setup for the bg command
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
start: () => Promise<[number, number]>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Adds a background image to the canvas.
|
|
12
|
+
* @param container The container to add the image to.
|
|
13
|
+
* @param renderer The renderer to use for the image.
|
|
14
|
+
* @param background The background image to add.
|
|
15
|
+
* @param resize The function to resize the canvas.
|
|
16
|
+
* @returns A command that can be used to undo the action.
|
|
17
|
+
*/
|
|
18
|
+
export declare function add_bg_image(container: Container, renderer: IRenderer, background: Blob | File, resize: (width: number, height: number) => void): BgImageCommand;
|
|
19
|
+
/**
|
|
20
|
+
* Command that sets a background
|
|
21
|
+
*/
|
|
22
|
+
interface BgColorCommand extends Command {
|
|
23
|
+
/**
|
|
24
|
+
* Initial setup for the bg command
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
start: () => [number, number];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Adds a background color to the canvas.
|
|
31
|
+
* @param container The container to add the image to.
|
|
32
|
+
* @param renderer The renderer to use for the image.
|
|
33
|
+
* @param color The background color to add.
|
|
34
|
+
* @param width The width of the background.
|
|
35
|
+
* @param height The height of the background.
|
|
36
|
+
* @param resize The function to resize the canvas.
|
|
37
|
+
* @returns A command that can be used to undo the action.
|
|
38
|
+
*/
|
|
39
|
+
export declare function add_bg_color(container: Container, renderer: IRenderer, color: ColorSource, width: number, height: number, resize: (width: number, height: number) => void): BgColorCommand;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Sprite, Texture, DisplayObject, Color, RenderTexture } from "pixi.js";
|
|
2
|
+
import {} from "../utils/commands";
|
|
3
|
+
import { make_graphics } from "../utils/pixi";
|
|
4
|
+
/**
|
|
5
|
+
* Adds a background image to the canvas.
|
|
6
|
+
* @param container The container to add the image to.
|
|
7
|
+
* @param renderer The renderer to use for the image.
|
|
8
|
+
* @param background The background image to add.
|
|
9
|
+
* @param resize The function to resize the canvas.
|
|
10
|
+
* @returns A command that can be used to undo the action.
|
|
11
|
+
*/
|
|
12
|
+
export function add_bg_image(container, renderer, background, resize) {
|
|
13
|
+
let sprite;
|
|
14
|
+
return {
|
|
15
|
+
async start() {
|
|
16
|
+
container.removeChildren();
|
|
17
|
+
const img = await createImageBitmap(background);
|
|
18
|
+
const bitmap_texture = Texture.from(img);
|
|
19
|
+
sprite = new Sprite(bitmap_texture);
|
|
20
|
+
return [sprite.width, sprite.height];
|
|
21
|
+
},
|
|
22
|
+
async execute() {
|
|
23
|
+
// renderer.resize(sprite.width, sprite.height);
|
|
24
|
+
resize(sprite.width, sprite.height);
|
|
25
|
+
sprite.zIndex = 0;
|
|
26
|
+
container.addChild(sprite);
|
|
27
|
+
},
|
|
28
|
+
undo() {
|
|
29
|
+
container.removeChildAt(0);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Adds a background color to the canvas.
|
|
35
|
+
* @param container The container to add the image to.
|
|
36
|
+
* @param renderer The renderer to use for the image.
|
|
37
|
+
* @param color The background color to add.
|
|
38
|
+
* @param width The width of the background.
|
|
39
|
+
* @param height The height of the background.
|
|
40
|
+
* @param resize The function to resize the canvas.
|
|
41
|
+
* @returns A command that can be used to undo the action.
|
|
42
|
+
*/
|
|
43
|
+
export function add_bg_color(container, renderer, color, width, height, resize) {
|
|
44
|
+
let sprite;
|
|
45
|
+
return {
|
|
46
|
+
start() {
|
|
47
|
+
container.removeChildren();
|
|
48
|
+
const graphics = make_graphics(1);
|
|
49
|
+
const texture = RenderTexture.create({
|
|
50
|
+
width,
|
|
51
|
+
height
|
|
52
|
+
});
|
|
53
|
+
graphics.beginFill(new Color(color));
|
|
54
|
+
graphics.drawRect(0, 0, width, height);
|
|
55
|
+
graphics.endFill();
|
|
56
|
+
renderer.render(graphics, { renderTexture: texture });
|
|
57
|
+
sprite = new Sprite(texture);
|
|
58
|
+
return [sprite.width, sprite.height];
|
|
59
|
+
},
|
|
60
|
+
async execute() {
|
|
61
|
+
resize(sprite.width, sprite.height);
|
|
62
|
+
sprite.zIndex = 1;
|
|
63
|
+
container.addChild(sprite);
|
|
64
|
+
},
|
|
65
|
+
undo() {
|
|
66
|
+
container.removeChildren();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type tool = "draw" | "erase" | "crop" | "bg";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type Writable } from "svelte/store";
|
|
2
|
+
/**
|
|
3
|
+
* Base command interface that is added to the command_managers history
|
|
4
|
+
*/
|
|
5
|
+
export interface Command {
|
|
6
|
+
/**
|
|
7
|
+
* Optionally called when the command is first executed for multi-step commands
|
|
8
|
+
* @param args arguments to pass to the command
|
|
9
|
+
*/
|
|
10
|
+
start?: (...args: any) => any | Promise<any>;
|
|
11
|
+
/**
|
|
12
|
+
* Optionally called when the command is continued for multi-step commands
|
|
13
|
+
* @param args arguments to pass to the command
|
|
14
|
+
*/
|
|
15
|
+
continue?: (...args: any) => any | Promise<any>;
|
|
16
|
+
/**
|
|
17
|
+
* Optionally called when the command is stopped for multi-step commands
|
|
18
|
+
* @param args arguments to pass to the command
|
|
19
|
+
*/
|
|
20
|
+
stop?: (...args: any) => any | Promise<any>;
|
|
21
|
+
/**
|
|
22
|
+
* Called by the command manager to execute the command, can act as a no-op if the work has already been done
|
|
23
|
+
* This function must be able to recreate the command if the command is undone and redone (`stop`/`start`/`continue` will not be called again)
|
|
24
|
+
*/
|
|
25
|
+
execute(): any | Promise<any>;
|
|
26
|
+
/**
|
|
27
|
+
* Called by the command manager to undo the command
|
|
28
|
+
* This function must be able to undo the work done by the execute function
|
|
29
|
+
*/
|
|
30
|
+
undo(): any | Promise<any>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Command manager interface that handles the undo/redo history
|
|
34
|
+
*/
|
|
35
|
+
export interface CommandManager {
|
|
36
|
+
/**
|
|
37
|
+
* Undo the last command
|
|
38
|
+
*/
|
|
39
|
+
undo(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Redo the last undone command
|
|
42
|
+
*/
|
|
43
|
+
redo(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Execute a command and add it to the history
|
|
46
|
+
* @param command command to execute
|
|
47
|
+
*/
|
|
48
|
+
execute(command: Command): void;
|
|
49
|
+
/**
|
|
50
|
+
* Whether or not there are commands that can be undone
|
|
51
|
+
* Observable store that you can subscribe to for updates
|
|
52
|
+
*/
|
|
53
|
+
readonly can_undo: Writable<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Whether or not there are commands that can be redone
|
|
56
|
+
* Observable store that you can subscribe to for updates
|
|
57
|
+
*/
|
|
58
|
+
readonly can_redo: Writable<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Resets the history
|
|
61
|
+
*/
|
|
62
|
+
reset(): void;
|
|
63
|
+
/**
|
|
64
|
+
* The current history node
|
|
65
|
+
* Observable store that you can subscribe to for updates
|
|
66
|
+
*/
|
|
67
|
+
readonly current_history: Writable<CommandNode>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Command node interface that is used to create the undo/redo history
|
|
71
|
+
*/
|
|
72
|
+
interface CommandNode {
|
|
73
|
+
/**
|
|
74
|
+
* Command that the node holds
|
|
75
|
+
*/
|
|
76
|
+
command: Command | null;
|
|
77
|
+
/**
|
|
78
|
+
* Next command in the history
|
|
79
|
+
*/
|
|
80
|
+
next: CommandNode | null;
|
|
81
|
+
/**
|
|
82
|
+
* Previous command in the history
|
|
83
|
+
*/
|
|
84
|
+
previous: CommandNode | null;
|
|
85
|
+
/**
|
|
86
|
+
* Push a command onto the history
|
|
87
|
+
* @param command command to push onto the history
|
|
88
|
+
*/
|
|
89
|
+
push(command: Command): void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Creates a command manager
|
|
93
|
+
* @returns a command manager
|
|
94
|
+
*/
|
|
95
|
+
export declare function command_manager(): CommandManager;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { writable } from "svelte/store";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a command node
|
|
4
|
+
* @param command command to add to the node
|
|
5
|
+
* @returns a command node
|
|
6
|
+
*/
|
|
7
|
+
function command_node(command) {
|
|
8
|
+
return {
|
|
9
|
+
command: command || null,
|
|
10
|
+
next: null,
|
|
11
|
+
previous: null,
|
|
12
|
+
push: function (command) {
|
|
13
|
+
const node = command_node(command);
|
|
14
|
+
node.previous = this;
|
|
15
|
+
this.next = node;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a command manager
|
|
21
|
+
* @returns a command manager
|
|
22
|
+
*/
|
|
23
|
+
export function command_manager() {
|
|
24
|
+
let history = command_node();
|
|
25
|
+
const can_undo = writable(false);
|
|
26
|
+
const can_redo = writable(false);
|
|
27
|
+
const current_history = writable(history);
|
|
28
|
+
return {
|
|
29
|
+
undo: function () {
|
|
30
|
+
if (history.previous) {
|
|
31
|
+
history.command?.undo();
|
|
32
|
+
history = history.previous;
|
|
33
|
+
}
|
|
34
|
+
can_undo.set(!!history.previous);
|
|
35
|
+
can_redo.set(!!history.next);
|
|
36
|
+
current_history.set(history);
|
|
37
|
+
},
|
|
38
|
+
redo: function () {
|
|
39
|
+
if (history.next) {
|
|
40
|
+
history.next.command?.execute();
|
|
41
|
+
history = history.next;
|
|
42
|
+
}
|
|
43
|
+
can_undo.set(!!history.previous);
|
|
44
|
+
can_redo.set(!!history.next);
|
|
45
|
+
current_history.set(history);
|
|
46
|
+
},
|
|
47
|
+
execute: function (command) {
|
|
48
|
+
command.execute();
|
|
49
|
+
history.push(command);
|
|
50
|
+
history = history.next;
|
|
51
|
+
can_undo.set(!!history.previous);
|
|
52
|
+
can_redo.set(!!history.next);
|
|
53
|
+
current_history.set(history);
|
|
54
|
+
},
|
|
55
|
+
can_undo,
|
|
56
|
+
can_redo,
|
|
57
|
+
current_history,
|
|
58
|
+
reset: function () {
|
|
59
|
+
history = command_node();
|
|
60
|
+
can_undo.set(false);
|
|
61
|
+
can_redo.set(false);
|
|
62
|
+
current_history.set(history);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action to handle clicks outside of a DOM node
|
|
3
|
+
* @param node DOM node to check the click is outside of
|
|
4
|
+
* @param callback callback function to call if click is outside
|
|
5
|
+
* @returns svelte action return object with destroy method to remove event listener
|
|
6
|
+
*/
|
|
7
|
+
export declare function click_outside(node: Node, callback: (arg: MouseEvent) => void): any;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { tick } from "svelte";
|
|
2
|
+
/**
|
|
3
|
+
* Svelte action to handle clicks outside of a DOM node
|
|
4
|
+
* @param node DOM node to check the click is outside of
|
|
5
|
+
* @param callback callback function to call if click is outside
|
|
6
|
+
* @returns svelte action return object with destroy method to remove event listener
|
|
7
|
+
*/
|
|
8
|
+
export function click_outside(node, callback) {
|
|
9
|
+
const handle_click = (event) => {
|
|
10
|
+
if (node &&
|
|
11
|
+
!node.contains(event.target) &&
|
|
12
|
+
!event.defaultPrevented) {
|
|
13
|
+
callback(event);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener("mousedown", handle_click, true);
|
|
17
|
+
return {
|
|
18
|
+
destroy() {
|
|
19
|
+
document.removeEventListener("mousedown", handle_click, true);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|