M3st3rJ4k3l commited on
Commit
72fdad7
Β·
verified Β·
1 Parent(s): cf4feb8

Upload 2 files

Browse files

Added functionality to cropping tab

Files changed (2) hide show
  1. app.py +66 -0
  2. image_utils.py +89 -1
app.py CHANGED
@@ -10,6 +10,7 @@ import threading
10
  import gradio as gr
11
  import spaces
12
  import torch
 
13
  from PIL import Image
14
 
15
  # ── Local modules β€” single source of truth for each concern ─────────────────
@@ -48,6 +49,8 @@ from image_utils import (
48
  build_pnginfo,
49
  push_pil_to_base,
50
  push_pil_to_reference,
 
 
51
  )
52
  from control_tools import (
53
  generate_depthmap,
@@ -639,6 +642,33 @@ with gr.Blocks() as demo:
639
  send_to_base_btn = gr.Button("β†’ Send to Base Image", variant="primary")
640
  send_to_ref_btn = gr.Button("β†’ Add to Reference Images")
641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  # ── Bulk processing tab ──────────────────────────────────────────
643
  with gr.Tab("πŸ“¦ Bulk Process", id="tab_bulk"):
644
  gr.Markdown(
@@ -815,6 +845,42 @@ with gr.Blocks() as demo:
815
  outputs=[reference_images],
816
  ).then(fn=on_reference_change, inputs=[reference_images], outputs=[reference_info])
817
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
  # ── Bulk tab wiring ──────────────────────────────────────────────────────
819
  bulk_event = bulk_run_btn.click(
820
  fn=bulk_infer,
 
10
  import gradio as gr
11
  import spaces
12
  import torch
13
+ import numpy as np
14
  from PIL import Image
15
 
16
  # ── Local modules β€” single source of truth for each concern ─────────────────
 
49
  build_pnginfo,
50
  push_pil_to_base,
51
  push_pil_to_reference,
52
+ extend_editor_canvas,
53
+ render_extend_schematic,
54
  )
55
  from control_tools import (
56
  generate_depthmap,
 
642
  send_to_base_btn = gr.Button("β†’ Send to Base Image", variant="primary")
643
  send_to_ref_btn = gr.Button("β†’ Add to Reference Images")
644
 
645
+ # ── Extend canvas section ────────────────────────────────────────────────
646
+ # Uses the editor's current composite as the source so cropping + painting
647
+ # happen first, then we grow the canvas around the result. Output is
648
+ # loaded back into the same editor β€” Send β†’ Base / Reference from there.
649
+ with gr.Accordion("πŸ“ Extend canvas (add padding around image)", open=True):
650
+ gr.Markdown(
651
+ "Grow the editor image's canvas by a percentage in any combination "
652
+ "of directions. Percentages are relative to the *current* image "
653
+ "size β€” `Down = 100` doubles the height with the image on top. "
654
+ "The result replaces the editor contents so you can crop again or "
655
+ "send it to Base / Reference with the buttons above."
656
+ )
657
+ with gr.Row():
658
+ ext_up = gr.Number(label="Up %", value=0, minimum=0, precision=2)
659
+ ext_down = gr.Number(label="Down %", value=0, minimum=0, precision=2)
660
+ ext_left = gr.Number(label="Left %", value=0, minimum=0, precision=2)
661
+ ext_right = gr.Number(label="Right %", value=0, minimum=0, precision=2)
662
+ with gr.Row():
663
+ ext_fill = gr.ColorPicker(label="Fill colour", value="#000000")
664
+ extend_btn = gr.Button("πŸ“ Extend canvas", variant="primary")
665
+ with gr.Row():
666
+ ext_schematic = gr.Image(
667
+ label="Layout preview (red outline = current image)",
668
+ type="pil", interactive=False, height=220,
669
+ )
670
+ ext_info = gr.Markdown("*Upload something into the editor first.*")
671
+
672
  # ── Bulk processing tab ──────────────────────────────────────────
673
  with gr.Tab("πŸ“¦ Bulk Process", id="tab_bulk"):
674
  gr.Markdown(
 
845
  outputs=[reference_images],
846
  ).then(fn=on_reference_change, inputs=[reference_images], outputs=[reference_info])
847
 
848
+ # Extend-canvas wiring
849
+ # The schematic previews the *current editor composite* so users see live
850
+ # feedback as they nudge the percentages / fill colour.
851
+ def _editor_source_for_preview(editor_value):
852
+ if not editor_value or editor_value.get("composite") is None:
853
+ return None
854
+ comp = editor_value["composite"]
855
+ if isinstance(comp, np.ndarray):
856
+ from PIL import Image as _Image
857
+ comp = _Image.fromarray(comp)
858
+ return comp
859
+
860
+ def _update_extend_preview(editor_value, up, down, left, right, fill):
861
+ return render_extend_schematic(
862
+ _editor_source_for_preview(editor_value), up, down, left, right, fill,
863
+ )
864
+
865
+ _extend_preview_inputs = [editor, ext_up, ext_down, ext_left, ext_right, ext_fill]
866
+ _extend_preview_outputs = [ext_schematic, ext_info]
867
+ for _c in (ext_up, ext_down, ext_left, ext_right, ext_fill):
868
+ _c.change(fn=_update_extend_preview,
869
+ inputs=_extend_preview_inputs, outputs=_extend_preview_outputs)
870
+ # Also refresh whenever the editor content itself changes (upload, HEIC load,
871
+ # a fresh crop) β€” that way the schematic scale updates too.
872
+ editor.change(fn=_update_extend_preview,
873
+ inputs=_extend_preview_inputs, outputs=_extend_preview_outputs)
874
+
875
+ # Run: extend, then hand the new PIL back to the editor. `render_extend_
876
+ # schematic` re-runs via editor.change once the new image lands, so no
877
+ # extra .then() is needed for the preview.
878
+ extend_btn.click(
879
+ fn=extend_editor_canvas,
880
+ inputs=[editor, ext_up, ext_down, ext_left, ext_right, ext_fill],
881
+ outputs=[editor],
882
+ )
883
+
884
  # ── Bulk tab wiring ──────────────────────────────────────────────────────
885
  bulk_event = bulk_run_btn.click(
886
  fn=bulk_infer,
image_utils.py CHANGED
@@ -10,7 +10,7 @@ import tempfile
10
  from typing import Any
11
 
12
  import numpy as np
13
- from PIL import Image, ImageOps, ImageFilter
14
  from PIL.PngImagePlugin import PngInfo
15
  import gradio as gr
16
 
@@ -316,3 +316,91 @@ def push_pil_to_reference(img, current_gallery):
316
  current.append(img)
317
  gr.Info("Added to Reference Images.")
318
  return current
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  from typing import Any
11
 
12
  import numpy as np
13
+ from PIL import Image, ImageOps, ImageFilter, ImageDraw, ImageColor
14
  from PIL.PngImagePlugin import PngInfo
15
  import gradio as gr
16
 
 
316
  current.append(img)
317
  gr.Info("Added to Reference Images.")
318
  return current
319
+
320
+ # ── Canvas extension (used by the Crop / Fix tab) ───────────────────────────
321
+ # Percentages are relative to the *original* dimensions. Padding is filled
322
+ # with a flat colour. Returns a new PIL image; does NOT save to disk β€” the
323
+ # result is loaded straight back into the ImageEditor so the existing
324
+ # Send β†’ Base / Send β†’ Reference buttons handle export.
325
+
326
+ from PIL import ImageColor # add near the other PIL imports at top of file
327
+
328
+
329
+ def _parse_fill(color_str: str, mode: str):
330
+ rgba = ImageColor.getcolor(color_str or "#000000", "RGBA")
331
+ if mode == "RGB":
332
+ return rgba[:3]
333
+ if mode == "L":
334
+ r, g, b, _ = rgba
335
+ return int(0.299 * r + 0.587 * g + 0.114 * b)
336
+ return rgba # RGBA
337
+
338
+
339
+ def compute_extend_padding(w: int, h: int, up, down, left, right) -> tuple[int, int, int, int]:
340
+ """Return (pad_left, pad_top, pad_right, pad_bottom) in pixels for the
341
+ given percentage inputs. Percentages are relative to original W/H so
342
+ Down=100 on 960Γ—960 β†’ 960Γ—1920 with the source at the top."""
343
+ pl = int(round(w * (float(left or 0) / 100.0)))
344
+ pr = int(round(w * (float(right or 0) / 100.0)))
345
+ pt = int(round(h * (float(up or 0) / 100.0)))
346
+ pb = int(round(h * (float(down or 0) / 100.0)))
347
+ return pl, pt, pr, pb
348
+
349
+
350
+ def extend_canvas(img: Image.Image, up, down, left, right, fill: str) -> Image.Image:
351
+ """Extend `img`'s canvas by the given per-side percentages, filling the
352
+ new area with `fill`. Original image mode is preserved so RGBA stays
353
+ RGBA (no transparency loss)."""
354
+ if img is None:
355
+ raise gr.Error("Nothing to extend β€” upload an image into the editor first.")
356
+ if img.mode not in ("RGB", "RGBA", "L"):
357
+ img = img.convert("RGBA")
358
+ for name, v in (("Up", up), ("Down", down), ("Left", left), ("Right", right)):
359
+ if v is None or float(v) < 0:
360
+ raise gr.Error(f"'{name} %' must be β‰₯ 0.")
361
+ w, h = img.size
362
+ pl, pt, pr, pb = compute_extend_padding(w, h, up, down, left, right)
363
+ if pl == pt == pr == pb == 0:
364
+ gr.Info("All percentages are 0 β€” image unchanged.")
365
+ return img
366
+ new_size = (w + pl + pr, h + pt + pb)
367
+ canvas = Image.new(img.mode, new_size, _parse_fill(fill, img.mode))
368
+ canvas.paste(img, (pl, pt))
369
+ return canvas
370
+
371
+
372
+ def render_extend_schematic(
373
+ img: Image.Image | None, up, down, left, right, fill: str,
374
+ max_dim: int = 320,
375
+ ) -> tuple[Image.Image | None, str]:
376
+ """Live, to-scale preview of what extend_canvas() will produce, without
377
+ doing the full render. Returns (schematic PIL, info markdown)."""
378
+ if img is None:
379
+ return None, "*Upload something into the editor first.*"
380
+ w, h = img.size
381
+ pl, pt, pr, pb = compute_extend_padding(w, h, up, down, left, right)
382
+ nw, nh = w + pl + pr, h + pt + pb
383
+ scale = min(max_dim / nw, max_dim / nh, 1.0)
384
+ sw, sh = max(1, int(nw * scale)), max(1, int(nh * scale))
385
+ spl, spt = int(pl * scale), int(pt * scale)
386
+ sow, soh = max(1, int(w * scale)), max(1, int(h * scale))
387
+
388
+ schem = Image.new("RGB", (sw, sh), _parse_fill(fill, "RGB"))
389
+ d = ImageDraw.Draw(schem) # ImageDraw already imported at top of module
390
+ d.rectangle([spl, spt, spl + sow - 1, spt + soh - 1],
391
+ fill=(200, 200, 200), outline=(255, 0, 0), width=2)
392
+ info = (f"**Original:** {w} Γ— {h} \n"
393
+ f"**Padding (L, T, R, B):** {pl}, {pt}, {pr}, {pb} \n"
394
+ f"**Final canvas:** {nw} Γ— {nh}")
395
+ return schem, info
396
+
397
+
398
+ def extend_editor_canvas(editor_value, up, down, left, right, fill) -> Image.Image:
399
+ """Take the current editor composite, extend it, and return the result so
400
+ it can be loaded straight back into the same ImageEditor. Any crop marks
401
+ the user had placed are baked in before extending (that's what
402
+ `_editor_composite` already does)."""
403
+ composite = fix_orientation(_editor_composite(editor_value))
404
+ out = extend_canvas(composite, up, down, left, right, fill)
405
+ gr.Info(f"Canvas extended to {out.size[0]} Γ— {out.size[1]}.")
406
+ return out