| <!-- SPDX-License-Identifier: GPL-3.0-or-later --> |
| # Easy Image Batch β behavior matrix |
|
|
| Status: **Canonical** (approved 2026-06-18). Implemented in |
| `k_easy_image_batch.py` and mirrored in |
| [`docs/user_guide/nodes/koolook_image/easy_image_batch.md`](../user_guide/nodes/koolook_image/easy_image_batch.md). |
|
|
| Runnable demo: the **Easy Image Batch β Select and Rebuild** workflow bundled in |
| the Kforge Labs Workflows sidebar folder |
| ([`docs/workflows/kforge-labs-workflows/`](../workflows/kforge-labs-workflows/), |
| from [#248](https://github.com/malkuthro/ComfyUI-Koolook/pull/248)). It |
| supersedes the generated demo removed here. |
|
|
| This captures the agreed changes: |
|
|
| 1. `keyframe_batch` β **`keyframes_insert`** (rename; the old name remains as a |
| deprecated alias input so workflows saved on 0.4.1/0.4.2 keep loading). |
| 2. **No image source connected β clean placeholder batch** sized by new |
| `width`/`height` widgets (no error). |
| 3. **Insert-over-source mode** β `keyframes_insert` + `source_batch` composite |
| the inserts over the source cut window. |
| 4. **`image1`β`image4` are "super-overwrites"** β when connected they replace |
| whatever is at their index in **every** mode (top priority). When **not** |
| connected they do **nothing** (the old "pull from `source_batch` via |
| `imageN_frame`" fallback β Mode 2 β is removed). |
| |
| --- |
| |
| ## The layered model (single source of truth) |
| |
| The output is built in three layers, lowest first. A later layer overwrites an |
| earlier one at the same output index. |
| |
| ``` |
| output index i β VFX frame v = cut_start_frame + i (window: i in [0, total_frames)) |
| |
| ββ Layer 3 β SUPER-OVERWRITE (image1βimage4) βββββββββββββββββ highest priority |
| β Each CONNECTED imageN β placed at index (imageN_frame β |
| β β cut_start_frame). Wins over everything. Unconnected β |
| β slots contribute NOTHING. (imageN_frame matters only β |
| β when imageN is connected.) β |
| ββ Layer 2 β SEQUENCE ββββββββββββββββββββββββββββββββββββββββ€ |
| β β’ keyframes_insert connected (INSERT): i-th incoming frame β |
| β β i-th source_frames position (ascending, deduped). β |
| β β’ else (SELECT): each source_frames number N picks β |
| β source_batch[Nβ1] β index (N β cut_start_frame). β |
| ββ Layer 1 β BACKGROUND ββββββββββββββββββββββββββββββββββββββ€ lowest priority |
| β β’ insert-over-source (keyframes_insert AND source_batch): β |
| β source_batch cut window (index i β source_batch[vβ1]; β |
| β frames beyond the source fall back to placeholder). β |
| β β’ otherwise: placeholder_color everywhere. β |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| ``` |
| |
| **Background rule.** Layer 1 is the source cut window whenever `source_batch` |
| is connected **and** either an insert is active (`keyframes_insert`) **or** the |
| `source_frames` list is empty; otherwise it is `placeholder_color`. |
|
|
| **What counts as "placed"** (drives `alpha_batch`, `selected_image_batch`, |
| `selected_frames`): normally the Layer 2 placements **and** Layer 3 overwrites; |
| the Layer 1 `source_batch` backdrop is *not* placed (just the background). So |
| `alpha_batch` is `0.0` at sequence inserts/picks and connected-slot overwrites, |
| `1.0` everywhere else (flipped by `invert_alpha`). |
|
|
| **Select passthrough is the exception (row 5).** When the source passes through |
| with an empty list, the covered source frames *are* kept content (`alpha` |
| `0.0`), and the **uncovered tail becomes the selection** (`alpha` `1.0`, |
| emitted in `selected_*`) β an extend-a-clip inpaint mask. The selection here is |
| the *gap*, the inverse of every other row. Insert-mode passthrough (row 9) |
| keeps the plain backdrop semantics (nothing placed). |
|
|
| **Resolution / dtype** come from the first connected image (insert: from |
| `keyframes_insert`; select: `image1` β `source_batch` β `image2β¦4`). With **no** |
| image connected at all, the output is sized by the `width`/`height` widgets |
| (channels = 3), on CPU/float β a clean placeholder batch. |
|
|
| **Tie-break:** if two connected slots target the same index, the |
| higher-numbered slot wins (`image4` > β¦ > `image1`). Sequence + slot at the |
| same index β the slot wins (Layer 3 > Layer 2). |
|
|
| --- |
|
|
| ## Mode matrix |
|
|
| Legend β `keyframes_insert` = **KI**, `source_batch` = **SB**, `source_frames` |
| list = **list**, connected `image1β4` = **slots**. |
|
|
| | # | KI | SB | list | slots | Background | Placed (alpha 0.0) | `selected_*` | Mode label | |
| |---|----|----|------|-------|-----------|--------------------|--------------|-----------| |
| | 1 | β | β | β | none | placeholder (`width`Γ`height`) | nothing | empty | **clean batch** | |
| | 2 | β | β | has | none | placeholder | source picks at listed positions | the picks | select | |
| | 3 | β | β | β | some | placeholder | connected slots | the slots | select (slots) | |
| | 4 | β | β | has | some | placeholder | source picks **+** slots (slots win on overlap) | picks + slots | select | |
| | 5 | β | β | empty | none | **source cut window** | covered source (kept) | the uncovered **gap** | source passthrough / extend | |
| | 6 | β | β | has | none | placeholder | scattered inserts | the inserts | offset / reconstruct | |
| | 7 | β | β | empty | none | placeholder | nothing | empty | **clean** (nothing inserted) | |
| | 8 | β | β | has | none | **source cut window** | scattered inserts (overwrite source) | the inserts | insert-over-source | |
| | 9 | β | β | empty | none | **source cut window** | nothing | empty | source passthrough | |
| | 10 | β | β | has | some | **source cut window** | inserts **+** slots (slots win) | inserts + slots | insert-over-source | |
| | 11 | any | any | any | **set but NOT connected** | (per above) | **slots contribute nothing** | (per above) | (per above) | |
|
|
| Row **11** was the bug fixed here: a slot with a non-default `imageN_frame` |
| that is **not wired** has zero effect on the output. The old behaviour (an |
| unconnected slot pulling from `source_batch`) is gone. |
|
|
| --- |
|
|
| ## Edge cases & logging |
|
|
| - **List ranges.** `N-M` (e.g. `14-17`) expands inclusively; a descending |
| `M-N`, an over-wide range (more than 8192 frames), or any non-integer token |
| warns and is skipped. Applies in both modes. |
| - **List set but no image source** (select): the list is ignored and a clean |
| placeholder batch is returned, with a console warning (not a silent drop). |
| - **List token not in `source_batch`** (select): warn + skip that number. |
| - **List/insert position outside the cut window**: dropped, summarized once. |
| - **More inserts than list positions** / **more positions than inserts**: |
| reported as "extra insert frames" / "positions with no insert frame". |
| - **`source_batch` shorter than the cut window** (insert-over-source): the |
| uncovered tail frames stay placeholder; count reported. |
| - **Empty list** in an insert mode: clean placeholder (no SB) or clean source |
| passthrough (with SB) β console note "frame list is empty". |
| - **Mismatched H/W/C** among connected inputs: hard `ValueError` (unchanged). |
| - Every run still prints one summary line: mode, cut window, placed count, and |
| any of the notes above. |
|
|
| --- |
|
|
| ## Decisions (resolved) |
|
|
| 1. **Tie-break direction** β higher-numbered slot wins (`image4` > `image1`); |
| a slot beats a sequence pick / insert at the same index (Layer 3 > Layer 2). |
| 2. **Empty-list passthrough (select)** β `source_batch` + an empty list passes |
| the source cut window through (row 5). Covered source frames are kept content |
| (`alpha` 0.0); a source shorter than the output leaves a placeholder tail |
| that becomes the selection (`selected_*`) β an extend/inpaint mask. The |
| selection here is the *gap*, the inverse of other rows. |
| 3. **Mode-2 fallback removed** β an unconnected slot never pulls from |
| `source_batch`; `imageN_frame` is only a placement target for a wired image. |
| 4. **Range syntax** β the frame list accepts inclusive ranges (`14-17` β |
| 14,15,16,17) in both select and insert; descending/non-integer tokens warn. |
|
|