@gradio/imageeditor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,139 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from "svelte";
3
+ import { click_outside } from "../utils/events";
4
+
5
+ import { type Brush } from "./Brush.svelte";
6
+ import ColorPicker from "./ColorPicker.svelte";
7
+ import ColorSwatch from "./ColorSwatch.svelte";
8
+ import ColorField from "./ColorField.svelte";
9
+
10
+ export let colors: string[];
11
+ export let selected_color: string;
12
+ export let color_mode: Brush["color_mode"] | undefined = undefined;
13
+ export let recent_colors: (string | null)[] = [];
14
+
15
+ let color_picker = false;
16
+ let current_mode: "hex" | "rgb" | "hsl" = "hex";
17
+ let editing_index: number | null = null;
18
+
19
+ const dispatch = createEventDispatcher<{
20
+ click_outside: void;
21
+ }>();
22
+
23
+ function handle_color_selection(
24
+ {
25
+ index,
26
+ color
27
+ }: {
28
+ index: number | null;
29
+ color: string | null;
30
+ },
31
+ type: "core" | "user"
32
+ ): void {
33
+ if (type === "user" && !color) {
34
+ editing_index = index;
35
+ color_picker = true;
36
+ }
37
+
38
+ if (!color) return;
39
+ selected_color = color;
40
+
41
+ if (type === "core") {
42
+ color_picker = false;
43
+ }
44
+ }
45
+
46
+ function handle_color_change(color: string): void {
47
+ if (editing_index === null) return;
48
+ recent_colors[editing_index] = color;
49
+ }
50
+
51
+ $: handle_color_change(selected_color);
52
+ let width = 0;
53
+ let height = 0;
54
+ let c_width = 0;
55
+ let c_height = 0;
56
+ let wrap_el: HTMLDivElement;
57
+ let anchor_right = false;
58
+ let anchor_top = false;
59
+
60
+ $: {
61
+ if (wrap_el && (width || height || c_height || c_width)) {
62
+ const box = wrap_el.getBoundingClientRect();
63
+
64
+ anchor_right = box.width + 30 > width / 2;
65
+ anchor_top = box.y < 80;
66
+ }
67
+ }
68
+ </script>
69
+
70
+ <svelte:window bind:innerHeight={height} bind:innerWidth={width} />
71
+
72
+ <div
73
+ class="wrap"
74
+ class:padded={!color_picker}
75
+ use:click_outside={() => dispatch("click_outside")}
76
+ class:right={anchor_right}
77
+ class:top={anchor_top}
78
+ class:bottom={!anchor_top}
79
+ bind:this={wrap_el}
80
+ bind:clientWidth={c_width}
81
+ bind:clientHeight={c_height}
82
+ >
83
+ {#if color_mode === "defaults"}
84
+ {#if color_picker}
85
+ <ColorPicker bind:color={selected_color} />
86
+ <ColorField
87
+ bind:current_mode
88
+ color={selected_color}
89
+ on:close={() => (color_picker = false)}
90
+ on:selected={({ detail }) => (selected_color = detail)}
91
+ />
92
+ {/if}
93
+ {/if}
94
+ <ColorSwatch
95
+ bind:color_picker
96
+ {colors}
97
+ on:select={({ detail }) => handle_color_selection(detail, "core")}
98
+ on:edit={({ detail }) => handle_color_selection(detail, "user")}
99
+ user_colors={color_mode === "defaults" ? recent_colors : null}
100
+ {selected_color}
101
+ {current_mode}
102
+ />
103
+ </div>
104
+
105
+ <style>
106
+ .wrap {
107
+ width: 230px;
108
+ position: absolute;
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: 5px;
112
+ background: var(--background-fill-secondary);
113
+ border: 1px solid var(--block-border-color);
114
+
115
+ border-radius: var(--radius-md);
116
+ box-shadow:
117
+ 0 0 5px rgba(0, 0, 0, 0.1),
118
+ 0 5px 30px rgba(0, 0, 0, 0.2);
119
+ padding-bottom: var(--size-2);
120
+ pointer-events: all;
121
+ cursor: default;
122
+ z-index: var(--layer-top);
123
+ }
124
+
125
+ .bottom {
126
+ bottom: 85px;
127
+ }
128
+
129
+ .top {
130
+ top: 30px;
131
+ }
132
+
133
+ .right {
134
+ right: 10px;
135
+ }
136
+ .padded {
137
+ padding-top: var(--size-3);
138
+ }
139
+ </style>
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { click_outside } from "../utils/events";
3
+ import { createEventDispatcher } from "svelte";
4
+
5
+ export let selected_size: number;
6
+ export let min: number;
7
+ export let max: number;
8
+
9
+ const dispatch = createEventDispatcher<{
10
+ click_outside: void;
11
+ }>();
12
+
13
+ let width = 0;
14
+ let height = 0;
15
+ let c_width = 0;
16
+ let c_height = 0;
17
+ let wrap_el: HTMLDivElement;
18
+ let anchor_right = false;
19
+ let anchor_top = false;
20
+
21
+ $: {
22
+ if (wrap_el && (width || height || c_height || c_width)) {
23
+ const box = wrap_el.getBoundingClientRect();
24
+
25
+ anchor_right = box.width + 30 > width / 2;
26
+ anchor_top = box.y < 80;
27
+ }
28
+ }
29
+ </script>
30
+
31
+ <svelte:window bind:innerHeight={height} bind:innerWidth={width} />
32
+
33
+ <div
34
+ class="wrap"
35
+ use:click_outside={() => dispatch("click_outside")}
36
+ bind:this={wrap_el}
37
+ bind:clientWidth={c_width}
38
+ bind:clientHeight={c_height}
39
+ class:right={anchor_right}
40
+ class:top={anchor_top}
41
+ class:bottom={!anchor_top}
42
+ >
43
+ <input type="range" bind:value={selected_size} {min} {max} step={1} />
44
+ </div>
45
+
46
+ <style>
47
+ .wrap {
48
+ width: 180px;
49
+ position: absolute;
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: 5px;
53
+ background: var(--background-fill-secondary);
54
+ border: 1px solid var(--block-border-color);
55
+
56
+ border-radius: var(--radius-md);
57
+ box-shadow:
58
+ 0 0 5px rgba(0, 0, 0, 0.1),
59
+ 0 5px 30px rgba(0, 0, 0, 0.2);
60
+ padding: var(--size-2);
61
+ cursor: default;
62
+ }
63
+
64
+ .bottom {
65
+ bottom: 85px;
66
+ }
67
+
68
+ .top {
69
+ top: 30px;
70
+ }
71
+
72
+ .right {
73
+ right: 10px;
74
+ }
75
+ </style>
@@ -0,0 +1,172 @@
1
+ <script lang="ts">
2
+ import { Eyedropper } from "@gradio/icons";
3
+ import { createEventDispatcher } from "svelte";
4
+
5
+ import tinycolor from "tinycolor2";
6
+ export let color: string;
7
+
8
+ export let current_mode: "hex" | "rgb" | "hsl" = "hex";
9
+
10
+ const dispatch = createEventDispatcher<{
11
+ selected: string;
12
+ close: void;
13
+ }>();
14
+ const modes = [
15
+ ["Hex", "hex"],
16
+ ["RGB", "rgb"],
17
+ ["HSL", "hsl"]
18
+ ] as const;
19
+
20
+ function format_color(color: string, mode: "hex" | "rgb" | "hsl"): string {
21
+ if (mode === "hex") {
22
+ return tinycolor(color).toHexString();
23
+ } else if (mode === "rgb") {
24
+ return tinycolor(color).toRgbString();
25
+ }
26
+ return tinycolor(color).toHslString();
27
+ }
28
+
29
+ $: color_string = format_color(color, current_mode);
30
+ $: color_string && dispatch("selected", color_string);
31
+
32
+ function request_eyedropper(): void {
33
+ // @ts-ignore
34
+ const eyeDropper = new EyeDropper();
35
+
36
+ eyeDropper.open().then((result: { sRGBHex: string }) => {
37
+ color = result.sRGBHex;
38
+ });
39
+ }
40
+
41
+ //@ts-ignore
42
+ const eyedropper_supported = !!window.EyeDropper;
43
+
44
+ function handle_click(): void {
45
+ dispatch("selected", color_string);
46
+ dispatch("close");
47
+ }
48
+ </script>
49
+
50
+ <div class="input">
51
+ <button class="swatch" style:background={color} on:click={handle_click}
52
+ ></button>
53
+ <div>
54
+ <div class="input-wrap">
55
+ <input
56
+ type="text"
57
+ value={color_string}
58
+ on:change={(e) => (color = e.currentTarget.value)}
59
+ />
60
+
61
+ <button class="eyedropper" on:click={request_eyedropper}>
62
+ {#if eyedropper_supported}
63
+ <Eyedropper />
64
+ {/if}
65
+ </button>
66
+ </div>
67
+ <div class="buttons">
68
+ {#each modes as [label, value]}
69
+ <button
70
+ class="button"
71
+ class:active={current_mode === value}
72
+ on:click={() => (current_mode = value)}>{label}</button
73
+ >
74
+ {/each}
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <style>
80
+ .input {
81
+ display: flex;
82
+ align-items: center;
83
+ padding: 0 10px 15px;
84
+ }
85
+
86
+ .input input {
87
+ height: 30px;
88
+ width: 100%;
89
+ flex-shrink: 1;
90
+ border-bottom-left-radius: 0;
91
+ border: 1px solid var(--block-border-color);
92
+ letter-spacing: -0.05rem;
93
+ border-left: none;
94
+ border-right: none;
95
+ font-family: var(--font-mono);
96
+ font-size: var(--scale-000);
97
+ padding-left: 15px;
98
+ padding-right: 0;
99
+ background-color: var(--background-fill-secondary);
100
+ color: var(--block-label-text-color);
101
+ }
102
+
103
+ .swatch {
104
+ width: 50px;
105
+ height: 50px;
106
+ border-top-left-radius: 15px;
107
+ border-bottom-left-radius: 15px;
108
+ flex-shrink: 0;
109
+ border: 1px solid var(--block-border-color);
110
+ }
111
+
112
+ .buttons button {
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ border: 1px solid var(--block-border-color);
117
+ background: var(--background-fill-secondary);
118
+ padding: 3px 6px;
119
+ font-size: var(--scale-000);
120
+ cursor: pointer;
121
+ border-right: none;
122
+ width: 100%;
123
+ border-top: none;
124
+ }
125
+
126
+ .buttons button:first-child {
127
+ border-left: none;
128
+ }
129
+
130
+ .buttons button:last-child {
131
+ border-bottom-right-radius: 15px;
132
+ border-right: 1px solid var(--block-border-color);
133
+ }
134
+
135
+ .buttons button:hover {
136
+ background: var(--background-fill-secondary-hover);
137
+ font-weight: var(--weight-bold);
138
+ }
139
+
140
+ .buttons button.active {
141
+ background: var(--background-fill-secondary);
142
+ font-weight: var(--weight-bold);
143
+ }
144
+
145
+ .buttons {
146
+ height: 20px;
147
+
148
+ display: flex;
149
+ justify-content: stretch;
150
+ gap: 0px;
151
+ /* padding: 0 10px; */
152
+ }
153
+
154
+ .input-wrap {
155
+ display: flex;
156
+ }
157
+
158
+ .eyedropper {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ width: 25px;
163
+ height: 30px;
164
+ border-top-right-radius: 15px;
165
+ border: 1px solid var(--block-border-color);
166
+ border-left: none;
167
+ background: var(--background-fill-secondary);
168
+ height: 30px;
169
+ padding: 7px 7px 5px 0px;
170
+ cursor: pointer;
171
+ }
172
+ </style>
@@ -0,0 +1,205 @@
1
+ <script lang="ts">
2
+ import { tick } from "svelte";
3
+ import tinycolor from "tinycolor2";
4
+ import { clamp } from "../utils/pixi";
5
+
6
+ export let color = "rgb(255, 255, 255)";
7
+
8
+ let name = "world";
9
+ let sl_marker_pos = [0, 0];
10
+ let sl_rect: DOMRect | null = null;
11
+ let sl_moving = false;
12
+ let sl = [0, 0];
13
+
14
+ let hue = 0;
15
+ let hue_marker_pos = 0;
16
+ let hue_rect: DOMRect | null = null;
17
+ let hue_moving = false;
18
+
19
+ function handle_hue_down(
20
+ event: MouseEvent & { currentTarget: HTMLDivElement }
21
+ ): void {
22
+ hue_rect = event.currentTarget.getBoundingClientRect();
23
+
24
+ hue_moving = true;
25
+ update_hue_from_mouse(event.clientX);
26
+ }
27
+
28
+ function update_hue_from_mouse(x: number): void {
29
+ if (!hue_rect) return;
30
+ const _x = clamp(x - hue_rect.left, 0, hue_rect.width); // Get the x-coordinate relative to the box
31
+ hue_marker_pos = _x;
32
+ const _hue = (_x / hue_rect.width) * 360; // Scale the x position to a hue value (0-360)
33
+
34
+ hue = _hue;
35
+
36
+ color = hsva_to_rgba({ h: _hue, s: sl[0], v: sl[1], a: 1 });
37
+ }
38
+
39
+ function hsva_to_rgba(hsva: {
40
+ h: number;
41
+ s: number;
42
+ v: number;
43
+ a: number;
44
+ }): string {
45
+ const saturation = hsva.s;
46
+ const value = hsva.v;
47
+ let chroma = saturation * value;
48
+ const hue_by_60 = hsva.h / 60;
49
+ let x = chroma * (1 - Math.abs((hue_by_60 % 2) - 1));
50
+ const m = value - chroma;
51
+
52
+ chroma = chroma + m;
53
+ x = x + m;
54
+
55
+ const index = Math.floor(hue_by_60) % 6;
56
+ const red = [chroma, x, m, m, x, chroma][index];
57
+ const green = [x, chroma, chroma, x, m, m][index];
58
+ const blue = [m, m, x, chroma, chroma, x][index];
59
+
60
+ return `rgba(${red * 255}, ${green * 255}, ${blue * 255}, ${hsva.a})`;
61
+ }
62
+
63
+ function update_color_from_mouse(x: number, y: number): void {
64
+ if (!sl_rect) return;
65
+ const _x = clamp(x - sl_rect.left, 0, sl_rect.width);
66
+ const _y = clamp(y - sl_rect.top, 0, sl_rect.height);
67
+ sl_marker_pos = [_x, _y];
68
+ const _hsva = {
69
+ h: hue * 1,
70
+ s: _x / sl_rect.width,
71
+ v: 1 - _y / sl_rect.height,
72
+ a: 1
73
+ };
74
+
75
+ sl = [_hsva.s, _hsva.v];
76
+
77
+ color = hsva_to_rgba(_hsva);
78
+ }
79
+
80
+ function handle_sl_down(
81
+ event: MouseEvent & { currentTarget: HTMLDivElement }
82
+ ): void {
83
+ sl_moving = true;
84
+ sl_rect = event.currentTarget.getBoundingClientRect();
85
+ update_color_from_mouse(event.clientX, event.clientY);
86
+ }
87
+
88
+ function handle_move(
89
+ event: MouseEvent & { currentTarget: EventTarget & Window }
90
+ ): void {
91
+ if (sl_moving) update_color_from_mouse(event.clientX, event.clientY);
92
+ if (hue_moving) update_hue_from_mouse(event.clientX);
93
+ }
94
+
95
+ function handle_end(): void {
96
+ if (sl_moving) sl_moving = false;
97
+ if (hue_moving) hue_moving = false;
98
+ }
99
+
100
+ async function update_mouse_from_color(color: string): Promise<void> {
101
+ if (sl_moving || hue_moving) return;
102
+ await tick();
103
+ if (!color) return;
104
+ if (!sl_rect) {
105
+ sl_rect = sl_wrap.getBoundingClientRect();
106
+ }
107
+
108
+ if (!hue_rect) {
109
+ hue_rect = hue_wrap.getBoundingClientRect();
110
+ }
111
+
112
+ const hsva = tinycolor(color).toHsv();
113
+ const _x = hsva.s * sl_rect!.width;
114
+ const _y = (1 - hsva.v) * sl_rect!.height;
115
+ sl_marker_pos = [_x, _y];
116
+ sl = [hsva.s, hsva.v];
117
+ hue = hsva.h;
118
+ hue_marker_pos = (hsva.h / 360) * hue_rect!.width;
119
+ }
120
+
121
+ $: update_mouse_from_color(color);
122
+
123
+ let sl_wrap: HTMLDivElement;
124
+ let hue_wrap: HTMLDivElement;
125
+ </script>
126
+
127
+ <svelte:window on:mousemove={handle_move} on:mouseup={handle_end} />
128
+
129
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
130
+ <div
131
+ class="color-gradient"
132
+ on:mousedown={handle_sl_down}
133
+ style="--hue:{hue}"
134
+ bind:this={sl_wrap}
135
+ >
136
+ <div
137
+ class="marker"
138
+ style:transform="translate({sl_marker_pos[0]}px,{sl_marker_pos[1]}px)"
139
+ style:background={color}
140
+ />
141
+ </div>
142
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
143
+ <div class="hue-slider" on:mousedown={handle_hue_down} bind:this={hue_wrap}>
144
+ <div
145
+ class="marker"
146
+ style:background={"hsl(" + hue + ", 100%, 50%)"}
147
+ style:transform="translateX({hue_marker_pos}px)"
148
+ />
149
+ </div>
150
+
151
+ <style>
152
+ .hue-slider {
153
+ position: relative;
154
+ width: 90%;
155
+ margin: 10px auto;
156
+ height: 10px;
157
+ border-radius: 5px;
158
+ background: linear-gradient(
159
+ to right,
160
+ hsl(0, 100%, 50%) 0%,
161
+ #ff0 17%,
162
+ lime 33%,
163
+ cyan 50%,
164
+ blue 67%,
165
+ magenta 83%,
166
+ red 100%
167
+ );
168
+ border-top: none;
169
+ }
170
+
171
+ .color-gradient {
172
+ position: relative;
173
+ --hue: white;
174
+
175
+ background: linear-gradient(rgba(0, 0, 0, 0), #000),
176
+ linear-gradient(90deg, #fff, hsl(var(--hue), 100%, 50%));
177
+ width: 100%;
178
+ height: 150px;
179
+ border-top-left-radius: 4px;
180
+ border-top-right-radius: 4px;
181
+ }
182
+
183
+ .marker {
184
+ position: absolute;
185
+ width: 14px;
186
+ height: 14px;
187
+ border-radius: 50%;
188
+ border: 2px solid white;
189
+
190
+ top: -7px;
191
+ left: -7px;
192
+ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
193
+ pointer-events: none;
194
+ }
195
+
196
+ .hue-slider > .marker {
197
+ box-shadow: 0 0 1px #888;
198
+ top: -3px;
199
+ margin: auto;
200
+ height: 16px;
201
+ width: 16px;
202
+ left: -8px;
203
+ border: 2px solid white;
204
+ }
205
+ </style>