@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 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 = 450;
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] | undefined;
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
- height={height || undefined}
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 || undefined}
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 = 450;
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
- height={height || undefined}
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 || undefined}
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}
@@ -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?: number | undefined;
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] | undefined;
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] | undefined;
157
+ get canvas_size(): [number, number];
157
158
  /**accessor*/
158
- set canvas_size(_: [number, number] | undefined);
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 = l * $editor_box.child_width;
172
- const cy = t * $editor_box.child_height;
173
- const cw = w * $editor_box.child_width;
174
- const ch = h * $editor_box.child_height;
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(l * $dimensions[0]),
187
- Math.round(t * $dimensions[1]),
188
- Math.round(w * $dimensions[0]),
189
- Math.round(h * $dimensions[1])
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="{$crop[2] * $editor_box.child_width + 1}px"
286
- style:height="{$crop[3] * $editor_box.child_height + 1}px"
287
- style:top="{$crop[1] * $editor_box.child_height +
288
- ($editor_box.child_top - $editor_box.parent_top) -
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 height = 450;
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
- const blobs = await editor.get_blobs();
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
- max_height={height}
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] | undefined;
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
- height?: number | undefined;
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.mask_container, crop, 0.2);
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.mask_container,
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 max_height;
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
- max_height
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="{$editor_box.child_height +
165
- ($editor_box.child_top - $editor_box.parent_top)}px"
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
- max_height: number;
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, mask_container: Container, crop: Writable<[number, number, number, number]>, current_opacity?: number): CropCommand;
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, mask_container, crop, current_opacity = 0) {
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
- mask_container.mask = sprite;
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
- * 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.
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 add_bg_image(container: Container, renderer: IRenderer, background: Blob | File, resize: (width: number, height: number) => void, max_height?: number): BgImageCommand;
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
- * 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.
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 add_bg_image(container, renderer, background, resize, max_height = 450) {
13
- let sprite;
14
- function calculate_dimensions(width, height, max_height) {
15
- const MAX_HEIGHT = max_height * 1.5;
16
- const MAX_WIDTH = MAX_HEIGHT * 2;
17
- let new_width = width;
18
- let new_height = height;
19
- if (height > MAX_HEIGHT) {
20
- const ratio = MAX_HEIGHT / height;
21
- new_width = width * ratio;
22
- new_height = MAX_HEIGHT;
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
- if (new_width > MAX_WIDTH) {
25
- const ratio = MAX_WIDTH / new_width;
26
- new_width = MAX_WIDTH;
27
- new_height = new_height * ratio;
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
- const [new_width, new_height] = calculate_dimensions(sprite.width, sprite.height, max_height);
38
- sprite.width = new_width;
39
- sprite.height = new_height;
40
- return [new_width, new_height];
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
- // renderer.resize(sprite.width, sprite.height);
44
- resize(sprite.width, sprite.height);
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.2",
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/statustracker": "^0.10.1",
18
- "@gradio/upload": "^0.14.5",
16
+ "@gradio/image": "^0.20.2",
17
+ "@gradio/statustracker": "^0.10.2",
19
18
  "@gradio/utils": "^0.10.0",
20
- "@gradio/wasm": "^0.16.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 = l * $editor_box.child_width;
251
- const cy = t * $editor_box.child_height;
252
- const cw = w * $editor_box.child_width;
253
- const ch = h * $editor_box.child_height;
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(l * $dimensions[0]),
270
- Math.round(t * $dimensions[1]),
271
- Math.round(w * $dimensions[0]),
272
- Math.round(h * $dimensions[1])
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="{$crop[2] * $editor_box.child_width + 1}px"
382
- style:height="{$crop[3] * $editor_box.child_height + 1}px"
383
- style:top="{$crop[1] * $editor_box.child_height +
384
- ($editor_box.child_top - $editor_box.parent_top) -
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] | undefined;
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 height = 450;
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
- const blobs = await editor.get_blobs();
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
- max_height={height}
262
+ {fixed_canvas}
249
263
  ></Sources>
250
264
 
251
265
  {#if transforms.includes("crop")}
@@ -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!.mask_container, crop, 0.2);
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.mask_container,
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 max_height: number;
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
- max_height
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="{$editor_box.child_height +
191
- ($editor_box.child_top - $editor_box.parent_top)}px"
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"}
@@ -26,7 +26,7 @@ export interface CropCommand extends Command {
26
26
 
27
27
  export function crop_canvas(
28
28
  renderer: IRenderer,
29
- mask_container: Container,
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
- mask_container.mask = sprite;
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
  }
@@ -21,69 +21,113 @@ interface BgImageCommand extends Command {
21
21
  }
22
22
 
23
23
  /**
24
- * Adds a background image to the canvas.
25
- * @param container The container to add the image to.
26
- * @param renderer The renderer to use for the image.
27
- * @param background The background image to add.
28
- * @param resize The function to resize the canvas.
29
- * @returns A command that can be used to undo the action.
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
- max_height = 450
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
- const [new_width, new_height] = calculate_dimensions(
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
- max_height
115
+ canvas_size[0],
116
+ canvas_size[1]
78
117
  );
79
- sprite.width = new_width;
80
- sprite.height = new_height;
81
-
82
- return [new_width, new_height];
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
- // renderer.resize(sprite.width, sprite.height);
86
- resize(sprite.width, sprite.height);
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);