Skip to content

Commit 9963935

Browse files
author
LEv145
committed
v 2.1.0
Add `ImageCombineNode` Add `ImagesGridByColumnsNode` Add `ImagesGridByRowsNode` Add `create_image_grid_by_rows` util Add `create_image_grid_by_columns` util Add `create_image_grid` util Refactor code Update workflows Update images Update link in README.md Rename project
1 parent 2059c14 commit 9963935

File tree

15 files changed

+682
-879
lines changed

15 files changed

+682
-879
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# XYPlot: Comfy plugin
1+
# ImagesGrid: Comfy plugin
22

33

4-
![Image](./workflows/xy_plot_mini.png)
5-
[Workflows](./workflows/xy_plot_mini.json)
6-
![Image](./workflows/xy_plot_base.png)
7-
[Workflows](./workflows/xy_plot_base.json)
4+
![Image](./workflows/mini.png)
5+
[Workflows](./workflows/mini.json)
6+
![Image](./workflows/base.png)
7+
[Workflows](./workflows/base.json)
88

99

1010
## How to use
@@ -13,11 +13,11 @@
1313

1414
```
1515
cd custom_nodes # From comfy path
16-
git clone https://github.com/LEv145/XY-plot-comfy-plugin XYPlot
16+
git clone https://github.com/LEv145/images-grid-comfy-plugin ImagesGrid
1717
```
1818
### Update
1919

2020
```
21-
cd custom_nodes/XYPlot
21+
cd custom_nodes/ImagesGrid
2222
git pull
2323
```

__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
from .src import LatentCombineNode, XYPlotNode
1+
from .src import LatentCombineNode, ImagesGridByColumnsNode, ImagesGridByRowsNode, ImageCombineNode
22

33

44
NODE_CLASS_MAPPINGS = {
5-
"XYPlot": XYPlotNode,
65
"LatentCombine": LatentCombineNode,
6+
"ImagesGridByColumns": ImagesGridByColumnsNode,
7+
"ImagesGridByRows": ImagesGridByRowsNode,
8+
"ImageCombine": ImageCombineNode,
79
}

src/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
from .nodes.xy_plot import XYPlotNode
1+
from .nodes.images_grid import ImagesGridByColumnsNode, ImagesGridByRowsNode
22
from .nodes.latent_combine import LatentCombineNode
3+
from .nodes.image_combine import ImageCombineNode

src/base.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
import typing as t
2-
from dataclasses import dataclass
32

43

5-
class BasePlotNode():
6-
CATEGORY: str = "XYPlot"
4+
class BaseNode():
5+
CATEGORY: str = "ImagesGrid"
76
FUNCTION: str = "execute"
87

98

10-
@dataclass
11-
class KSamplerXYPlotInput():
12-
setting: str
13-
value: int
14-
15-
169
Image = t.Any

src/nodes/image_combine.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import typing as t
2+
3+
import torch
4+
5+
from ..base import BaseNode, Image
6+
7+
8+
class ImageCombineNode(BaseNode):
9+
RETURN_TYPES: t.Tuple[str] = ("IMAGE",)
10+
11+
@classmethod
12+
def INPUT_TYPES(cls) -> t.Dict[str, t.Any]:
13+
return {
14+
"required": {
15+
"image_1": ("IMAGE",),
16+
"image_2": ("IMAGE",),
17+
},
18+
}
19+
20+
def execute(
21+
self,
22+
image_1: Image,
23+
image_2: Image,
24+
) -> t.Tuple[Image]:
25+
print(image_1.size())
26+
print(image_2.size())
27+
print(image_1)
28+
29+
result = torch.cat((image_1, image_2), 0)
30+
print(result.size())
31+
32+
return (result,)

src/nodes/images_grid.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import typing as t
2+
3+
from ..base import BaseNode, Image
4+
from ..utils import (
5+
tensor_to_pillow,
6+
pillow_to_tensor,
7+
create_image_grid_by_columns,
8+
create_image_grid_by_rows,
9+
)
10+
11+
12+
class BaseImagesGridNode(BaseNode):
13+
RETURN_TYPES: t.Tuple[str] = ("IMAGE",)
14+
15+
@classmethod
16+
def _create_input_types(cls, coordinate_name: str) -> t.Dict[str, t.Any]:
17+
return {
18+
"required": {
19+
"images": ("IMAGE",),
20+
"gap": ("INT", {"default": 0, "min": 0}),
21+
coordinate_name: ("INT", {"default": 1, "min": 1}),
22+
}
23+
}
24+
25+
def _create_execute(self, images, function, function_kw) -> t.Tuple[Image]:
26+
pillow_images = [tensor_to_pillow(i) for i in images]
27+
pillow_grid = function(images=pillow_images, **function_kw)
28+
tensor_grid = pillow_to_tensor(pillow_grid)
29+
30+
return (tensor_grid,)
31+
32+
33+
class ImagesGridByColumnsNode(BaseImagesGridNode):
34+
@classmethod
35+
def INPUT_TYPES(cls) -> t.Dict[str, t.Any]:
36+
return cls._create_input_types("max_columns")
37+
38+
def execute(self, images: Image, **kw) -> tuple[Image]:
39+
return self._create_execute(images, create_image_grid_by_columns, kw)
40+
41+
42+
class ImagesGridByRowsNode(BaseImagesGridNode):
43+
@classmethod
44+
def INPUT_TYPES(cls) -> t.Dict[str, t.Any]:
45+
return cls._create_input_types("max_rows")
46+
47+
def execute(self, images: Image, **kw) -> tuple[Image]:
48+
return self._create_execute(images, create_image_grid_by_rows, kw)

src/nodes/latent_combine.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import torch
44

5-
from ..base import BasePlotNode, Image
5+
from ..base import BaseNode, Image
66

77

8-
class LatentCombineNode(BasePlotNode):
8+
class LatentCombineNode(BaseNode):
99
RETURN_TYPES: t.Tuple[str] = ("LATENT",)
1010

1111
@classmethod
@@ -22,8 +22,6 @@ def execute(
2222
latent_1: t.Dict[str, t.Any],
2323
latent_2: t.Dict[str, t.Any],
2424
) -> t.Tuple[t.Dict[str, t.Any]]:
25-
latent_1_samples = latent_1["samples"]
26-
latent_2_samples = latent_2["samples"]
27-
samples = torch.cat((latent_1_samples, latent_2_samples), 0)
25+
samples = torch.cat((latent_1["samples"], latent_2["samples"]), 0)
2826

2927
return ({"samples": samples},)

src/nodes/xy_plot.py

Lines changed: 0 additions & 30 deletions
This file was deleted.

src/utils.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,41 @@ def pillow_to_tensor(image):
1313
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
1414

1515

16-
def create_image_grid(images: t.List[Image.Image], gap: int, ncol: int):
17-
# Calculate the number of rows needed based on the number of images and columns
18-
nrow = (len(images) + ncol - 1) // ncol
19-
20-
# Get the size of the first image to use as a template for the grid
16+
def create_image_grid_by_columns(
17+
images: t.List[Image.Image],
18+
gap: int,
19+
max_columns: int,
20+
) -> Image.Image:
21+
max_rows = (len(images) + max_columns - 1) // max_columns
22+
return create_image_grid(images=images, gap=gap, max_columns=max_columns, max_rows=max_rows)
23+
24+
25+
def create_image_grid_by_rows(
26+
images: t.List[Image.Image],
27+
gap: int,
28+
max_rows: int,
29+
) -> Image.Image:
30+
max_columns = (len(images) + max_rows - 1) // max_rows
31+
return create_image_grid(images=images, gap=gap, max_columns=max_columns, max_rows=max_rows)
32+
33+
34+
def create_image_grid(
35+
images: t.List[Image.Image],
36+
gap: int,
37+
max_columns: int,
38+
max_rows: int,
39+
) -> Image.Image:
2140
size = images[0].size
2241

23-
# Calculate the total size of the grid with gaps
24-
width = size[0] * ncol + gap * (ncol - 1)
25-
height = size[1] * nrow + gap * (nrow - 1)
42+
width = size[0] * max_columns + (max_columns - 1) * gap
43+
height = size[1] * max_rows + (max_rows - 1) * gap
2644

27-
# Create a new image for the grid
2845
grid_image = Image.new("RGB", (width, height), color="white")
2946

30-
# Iterate over each image and paste it into the grid
3147
for i, image in enumerate(images):
32-
# Calculate the position of the image in the grid
33-
x = (i % ncol) * (size[0] + gap)
34-
y = (i // ncol) * (size[1] + gap)
48+
x = (i % max_columns) * (size[0] + gap)
49+
y = (i // max_columns) * (size[1] + gap)
3550

36-
# Paste the image into the grid
3751
grid_image.paste(image, (x, y))
3852

3953
return grid_image

0 commit comments

Comments
 (0)