panel sorting & updated workflows
- added SortPanels node to custom nodes - updated Manga Panel Extration Example workflows - added new workflow: WAN2.2 I2V Oneshot Animate All Panels Sequentially with FadeIn - updated project version to 1.1.0
@@ -1598,6 +1598,89 @@ class DetectPanelsInImage:
|
||||
return (panels,)
|
||||
|
||||
|
||||
class SortPanels:
|
||||
|
||||
CENTER_FUNCS = {
|
||||
"TOP-LEFT": lambda bxmin, bymin, bxmax, bymax: (bxmin, bymin),
|
||||
"CENTER": lambda bxmin, bymin, bxmax, bymax: ( (bxmin + bxmax)/2, (bymin + bymax)/2 ),
|
||||
"BOTTOM-RIGHT": lambda bxmin, bymin, bxmax, bymax: (bxmax, bymax),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"panels": (IO_Types.PANEL,),
|
||||
"left2right": ("BOOLEAN", {"default": True}),
|
||||
"center": (list(cls.CENTER_FUNCS.keys()), {"default": "TOP-LEFT",
|
||||
"tooltip": "point used for sorting."})
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
CATEGORY = CATEGORY_PATH
|
||||
RETURN_TYPES = (IO_Types.PANEL,)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_TOOLTIPS = ("Panels (Shapely Polygons)",)
|
||||
FUNCTION = "exec"
|
||||
DESCRIPTION = """
|
||||
Simple algo that attempts to sort panels by reading order according to
|
||||
their bounds center coordinates in quantized space.
|
||||
"""
|
||||
|
||||
def exec(self, panels, left2right, center):
|
||||
if not panels:
|
||||
return ([],)
|
||||
|
||||
if isinstance(left2right, list):
|
||||
left2right = left2right[0]
|
||||
|
||||
if isinstance(center, list):
|
||||
center = center[0]
|
||||
|
||||
# find global bounding box
|
||||
minx = min(p.bounds[0] for p in panels)
|
||||
miny = min(p.bounds[1] for p in panels)
|
||||
maxx = max(p.bounds[2] for p in panels)
|
||||
maxy = max(p.bounds[3] for p in panels)
|
||||
|
||||
n = len(panels)
|
||||
grid_size = n # number of grid cells in one dimension
|
||||
# if there is a bias towards columns or rows only, then non-quantized coordinates should suffice
|
||||
# thus is better to have a somewhat small n, under the supposition the "grid like" layout is balanced
|
||||
|
||||
# quantize space
|
||||
# compute grid cell dimensions
|
||||
cell_w = (maxx - minx) / grid_size if maxx != minx else 1.0
|
||||
cell_h = (maxy - miny) / grid_size if maxy != miny else 1.0
|
||||
|
||||
# assign each polygon to a grid cell
|
||||
poly_cells = []
|
||||
center_func = self.CENTER_FUNCS[center]
|
||||
for i, p in enumerate(panels):
|
||||
x, y = center_func(*p.bounds)
|
||||
|
||||
# compute grid indices (col, row)
|
||||
col = int((x - minx) // cell_w)
|
||||
row = int((maxy - y) // cell_h) # top-to-bottom order
|
||||
|
||||
# clamp indices
|
||||
col = max(0, min(grid_size - 1, col))
|
||||
row = max(0, min(grid_size - 1, row))
|
||||
|
||||
poly_cells.append((row, col, y, x, i, p))
|
||||
|
||||
# sort by row, then by column (reading order)
|
||||
if left2right:
|
||||
poly_cells.sort(key=lambda t: (-t[0], t[1], t[2], t[3])) # row, col, x, y(desc)
|
||||
else:
|
||||
poly_cells.sort(key=lambda t: (-t[0], -t[1], t[2], -t[3])) # reverse col order
|
||||
|
||||
# extract sorted polygons
|
||||
sorted_polygons = [t[-1] for t in poly_cells]
|
||||
return (sorted_polygons,)
|
||||
|
||||
|
||||
class InvertCardinals:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -1789,6 +1872,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"bmad_GridPanelLayoutGenerator": GridPanelLayoutGenerator,
|
||||
"bmad_MutatePanelLayout": MutatePanelLayout,
|
||||
"bmad_DetectPanelsInImage": DetectPanelsInImage,
|
||||
"bmad_SortPanels": SortPanels,
|
||||
|
||||
"bmad_PolygonBounds": PolygonBounds,
|
||||
"bmad_PolygonUnwrappedBounds": PolygonUnwrappedBounds,
|
||||
@@ -1847,6 +1931,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"bmad_GridPanelLayoutGenerator": "Grid Panel Layout Generator",
|
||||
"bmad_MutatePanelLayout": "Mutate Panel Layout",
|
||||
"bmad_DetectPanelsInImage": "Detect Panels In Image",
|
||||
"bmad_SortPanels": "Sort Panels",
|
||||
|
||||
"bmad_PolygonBounds": "Polygon.bounds",
|
||||
"bmad_PolygonUnwrappedBounds": "Polygon.bounds (unwrapped)",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_panels"
|
||||
description = "Comics/Manga like panel layouts."
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["shapely==2.1.1", "matplotlib==3.10.6", "opencv-python~=4.8.1.78"]
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 565 KiB After Width: | Height: | Size: 488 KiB |
|
Before Width: | Height: | Size: 581 KiB After Width: | Height: | Size: 499 KiB |
|
Before Width: | Height: | Size: 444 KiB After Width: | Height: | Size: 381 KiB |
|
After Width: | Height: | Size: 537 KiB |