Commit Graph
13 Commits
Author SHA1 Message Date
ausboss d877784132 Merge branch 'feat/reliability-hardening' into feat/editor-polish
# Conflicts:
#	js/shared/transform_editor.mjs
2026-08-12 22:56:33 -05:00
ausboss c612fd24ec Support user-editable aspect ratio presets 2026-08-12 22:55:32 -05:00
ausboss d4ceccf103 Add Alt+E command to open the selected transform editor 2026-08-12 22:54:14 -05:00
ausboss 098a57080d Parse fill_color tolerantly on both sides 2026-08-12 22:53:56 -05:00
ausboss c383fdf2f0 Notify the graph tracker after DOM widget commits 2026-08-12 22:51:26 -05:00
ausboss ac8cf4832c Attach rotate knob to the rotated image corner, feather QoL, live fade preview
The rotation knob previously anchored to axis-aligned bounding boxes, so
under rotation or padding it floated in space, visually attached to
nothing. It now rides the actual top-right corner of the rotated image
quad (short stem, offset outward along the corner's radial direction)
and orbits with the image as the angle changes - the grab-the-object's-
corner model design tools use. Verified at 0/30/-60/135 degrees: the
drawn knob sits exactly at the computed rotated-corner position.

Feather quality of life: default is now 24 (a no-op until padding or
rotation creates generated area), and the slider is paired with a number
box with single-step arrows for granular control; both stay in sync and
values above the slider range remain typeable up to 4096.

The sidebar Preview now renders the feather fade live: it builds the
generated-area mask (padding, rotation corners, source alpha), blurs it,
draws it twice to match the engine's edge-pinned ramp, tints it with the
fill color, and overlays - so tweaking feather shows the real blend as
you type. Verified: preview pixels near the crop edge move measurably
toward the fill color as feather increases.
2026-07-25 00:21:08 -05:00
ausboss c4d609cf7e Add sidebar section legends and a live output preview panel
Implements the mocked-up sidebar refinements: each section header now
carries a small marker matching its on-canvas handle (cyan square for
crop, green rotate knob, orange diamond for padding), teaching the
editor's color language without explanatory text, and a Preview section
at the bottom of the sidebar renders the actual output composite - fill
background, rotated source clipped to the crop, padded canvas, no
overlays - live while dragging any handle.

Browser-verified: all three markers render, sections appear in order
(Crop, Rotate, Padding & mask, View & reset, Preview), and the preview
canvas resizes and repaints when rotation and padding change, with fill
color in the padding corners and image content in the center.
2026-07-25 00:01:04 -05:00
ausboss e4a0d68573 Fix feather seam, anchor rotate knob to outermost corner, color key buttons
Feathering had two defects: the mask used max(original, blur), and a
blurred step edge sits at ~50% on the boundary, so the mask jumped
255->127 in one pixel - the visible seam line - and the output image was
never blended at all, ending in a hard cut against the fill. The mask
ramp is now pinned to full strength exactly at the edge (double the blur
and clip, then max with the original for thin slivers), and the visible
output fades into the fill color across the same ramp, so outpaints
blend smoothly in both the image and the mask.

The rotation knob now anchors to the outermost top-right corner of
source-union-output, so it stays visually attached when padding grows
the canvas instead of floating where the source corner used to be.

Save & close is now filled with the AusBoss accent; Reset all is styled
as a destructive red button.

22 Python + 9 JS tests pass, including new asserts that the mask ramp
holds >=200 on the first kept column (was ~127) and that the image
visibly blends into the fill. Live-browser verified: knob tracks the
outermost corner under 140/90px padding, buttons computed styles match.
2026-07-24 23:54:43 -05:00
ausboss 4c6bc8197e Fix play/pause race that made playback unpausable
Each playback tick awaits a frame fetch, and a Pause press usually lands
during that await. The tick only checked for cancellation at its start,
so after the await it re-scheduled itself anyway - a zombie loop the
button could no longer control, since the toggle keyed off the constantly
replaced timer handle. Repro was play, pause, play: from then on pause
presses landed mid-await and were overwritten.

Playback state is now an explicit `playing` flag (the toggle's single
source of truth) plus a session counter; ticks re-check both after the
await and die if the session changed, covering both pause and rapid
pause-then-play. Verified in a live browser with a deterministic harness
that clicks Pause at the exact moment a frame fetch starts: both race
cycles stop cleanly, no frame advancement while paused across 3s of
sampling, and normal play/pause still works.
2026-07-24 23:23:55 -05:00
ausboss 72c3447a51 Zero-latency scrub via storyboard, decoder sessions, and off-loop decodes
Real-world scrubbing was still sluggish on large files for three reasons
the small test clip hid: decodes ran synchronously inside async route
handlers (stalling ComfyUI's entire web server, websocket included),
every request re-opened and re-probed the container, and mid-GOP targets
re-decoded from the previous keyframe each time.

- Route handlers now run decode/encode in worker threads.
- A persistent per-file decoder session keeps the container and codec
  state alive: forward steps decode only the frames in between, backward
  or distant jumps do one keyframe seek without reopening. Sessions are
  LRU-capped and an idle reaper closes them after 60s so the file lock
  is released (Windows users can delete/overwrite a clip they just
  scrubbed) — a bug the test suite caught via WinError 32.
- A keyframe storyboard (up to 96 tiles) builds in a background thread
  when a video is selected. Dragging the timeline draws the nearest tile
  synchronously — measured 2.4ms input-to-pixels in a live browser —
  and the exact decoded frame replaces it when it lands.
- The rotation handle emoji is replaced with a vector arc-and-arrowhead
  glyph, crisp at any zoom and identical on every platform.

21 Python + 9 JS tests pass, including new coverage for session
forward/backward parity against fresh decodes and storyboard payloads.
2026-07-24 23:12:30 -05:00
ausboss f582933cc3 Make video scrubbing feel live and move the rotate handle to the corner
Scrubbing previously used a 70ms trailing debounce, so nothing rendered
while the slider was moving and stale responses could rewrite frame_index
mid-drag (rubber-banding). Replaced with a latest-wins pump: the first
input fetches immediately, exactly one request stays in flight, and each
response re-reads the widgets so the next fetch targets the newest
position. Scrub frames download at 640px with widget sync off; drag
release, key release, and playback stop land one full-size frame that
snaps the widgets to the decoded position. Playback and held arrow keys
share the light path. Server side, per-file metadata is cached by
(path, mtime, size) so a scrub request opens the container once, and the
JPEG optimize pass is dropped for ephemeral previews.

Browser-verified on live ComfyUI: 41 burst inputs -> 23 fetches rendering
continuously during the drag, final widget lands on the last position,
and the displayed pixels match the exact final frame. Random-access
frame latency measured at ~18ms median on the test clip.

The rotation control moves from a stalk above top-center (stacked on the
pad_top diamond) to a rotate-glyph knob off the source's top-right
corner, with the help text updated.
2026-07-24 22:28:59 -05:00
ausboss 7436cf0c12 Fix editor geometry parity, video seek performance, and preview route exposure
Review fixes verified against live ComfyUI 0.28.0:

- rotatedSize now reproduces Pillow's expand=True output exactly (transpose
  fast path at axis angles, corner extents elsewhere). The previous formula
  was 1px short on 34 of 78 sampled size/angle combos, so editor-predicted
  output dimensions disagreed with queued results at most free angles.
  Verified 0 mismatches across 6776 combinations and end-to-end on the
  server (predicted 848x496 == queued image and mask at 77.7 degrees).
- decode_video_frame seeks to the nearest keyframe and decodes forward
  instead of scanning from frame 0, with the sequential scan kept as the
  fallback for files without usable timestamps. Scrubbing long videos no
  longer decodes the entire prefix per preview request.
- Preview routes no longer serve arbitrary local paths to any HTTP client:
  local-path previews outside ComfyUI's input/output/temp folders now
  require AUSBOSS_TRANSFORM_LOCAL_PREVIEW=1, checked before touching the
  filesystem so denied requests cannot probe path existence. Queued
  workflows are unaffected.
- Editor: metadata is cached per source instead of refetched every scrub
  tick, timeline seeks fetch only the frame with a 70ms trailing debounce,
  AbortError from superseded requests no longer paints the error state,
  and switching source mode before picking a file no longer resets the
  transform.
- Tests: 13 -> 18 Python (seek vs sequential ground truth, time-mode seek,
  out-of-range clamp, preview gate), 8 -> 9 JS (Pillow-verified rotated-size
  fixtures including the odd/even transpose edge).
2026-07-24 22:00:27 -05:00
ausboss 83fae3b36b Build polished transform editors 2026-07-24 20:43:53 -05:00