23 Commits
Author SHA1 Message Date
LEv145 6c93712961 v 2.5.1
Add support `\n` for column texts
2024-02-23 01:09:19 +02:00
LEv145 9591638263 Merge pull request #9 from kenjiqq/main
Fix padding calculation for column annotations with newlines
2024-02-23 01:04:18 +02:00
kenjiqq bb195b17e2 fix padding calculation for column annotations with newlines 2024-02-18 15:17:09 +01:00
LEv145 44365fa3ea Merge pull request #8 from LEv145/dev
v 2.5
2023-08-05 16:52:22 +02:00
LEv145 d6fc97799f Merge pull request #7 from Lightsockie/main
Allow Single-Axis Annotations
2023-08-05 16:45:37 +02:00
LEv145 8f439e074e Update logic 2023-08-05 16:37:39 +02:00
LEv145 fe2447ef6c Update logic 2023-08-05 16:35:35 +02:00
LEv145 d4e337eeb3 Move vars 2023-08-05 16:31:28 +02:00
LEv145 4548bf62a4 Update typing 2023-08-05 16:30:14 +02:00
LEv145 63ddd51d0b Update typing 2023-08-05 16:28:49 +02:00
lightsockie aefa323bab Allow Single-Axis Annotations 2023-08-04 15:04:52 -07:00
LEv145 f80fdb3a14 Merge pull request #5 from LEv145/dev
v 2.4
2023-04-23 17:02:11 +02:00
LEv145 ed05cffed4 v 2.4.0 2023-04-23 17:01:13 +02:00
LEv145 71d14ea040 v 2.3.3
Remove `COLUMNS_COUNT`, `ROWS_COUNT` for `GridAnnotationNode` output
2023-04-23 16:19:02 +02:00
LEv145 cfe6b34c87 v 2.3.2
Remove `COLUMNS_COUNT`, `ROWS_COUNT` for `GridAnnotationNode` output
2023-04-23 16:18:38 +02:00
LEv145 38018edd1b v 2.3.2
Add multiply lines in `GridAnnotationNode`
Fix typing
Add `COLUMNS_COUNT`, `ROWS_COUNT` for `GridAnnotationNode` output
2023-04-23 16:13:00 +02:00
LEv145 e662eab633 v 2.3.1
Fix paddings for grid annotations
Add `\n` support for text
2023-04-23 15:12:18 +02:00
LEv145 0358455f5b Merge pull request #4 from LEv145/dev
v 2.3.0
2023-04-19 17:54:35 +02:00
LEv145 8404dc8c50 v 2.3.0
Update version
2023-04-19 17:54:17 +02:00
LEv145 afba0bde3e Merge pull request #3 from LEv145/dev
v 2.2.4
2023-04-19 17:52:36 +02:00
LEv145 b7c0fd1b30 v 2.2.4
Fix typo
2023-04-19 17:52:05 +02:00
LEv145 5ef24f2994 Merge pull request #2 from LEv145/dev
v 2.2.3
2023-04-19 17:51:30 +02:00
LEv145 2a4be6c78c v 2.2.3
Add `\n` support (Beta)
Update README
Add `workflow` for `efficiency-nodes-comfyui`
2023-04-19 17:49:53 +02:00
12 changed files with 537 additions and 50 deletions
+1
View File
@@ -158,3 +158,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

+7
View File
@@ -3,6 +3,8 @@
## Preview ## Preview
![Image](https://github.com/LEv145/images-grid-comfy-plugin/blob/main/.readme/preview.png?raw=true)
### Simple grid of images ### Simple grid of images
![Image](https://github.com/LEv145/images-grid-comfy-plugin/blob/main/workflows/mini.png?raw=true) ![Image](https://github.com/LEv145/images-grid-comfy-plugin/blob/main/workflows/mini.png?raw=true)
@@ -11,6 +13,11 @@
![Image](https://github.com/LEv145/images-grid-comfy-plugin/blob/main/workflows/base.png?raw=true) ![Image](https://github.com/LEv145/images-grid-comfy-plugin/blob/main/workflows/base.png?raw=true)
### Integration with [`efficiency`](https://github.com/LucianoCirino/efficiency-nodes-comfyui)
![Image](https://github.com/LEv145/images-grid-comfy-plugin/blob/main/workflows/efficiency.png?raw=true)
Workflows: https://github.com/LEv145/images-grid-comfy-plugin/tree/main/workflows Workflows: https://github.com/LEv145/images-grid-comfy-plugin/tree/main/workflows
+5 -14
View File
@@ -7,14 +7,14 @@ from ..utils import Annotation
class GridAnnotationNode(BaseNode): class GridAnnotationNode(BaseNode):
RETURN_TYPES: tuple[str] = ("GRID_ANNOTATION",) RETURN_TYPES: tuple[str, ...] = ("GRID_ANNOTATION",)
@classmethod @classmethod
def INPUT_TYPES(cls) -> dict[str, t.Any]: def INPUT_TYPES(cls) -> dict[str, t.Any]:
return { return {
"required": { "required": {
"column_texts": ("STRING", {"multiline": False}), "column_texts": ("STRING", {"multiline": True}),
"row_texts": ("STRING", {"multiline": False}), "row_texts": ("STRING", {"multiline": True}),
"font_size": ("INT", {"default": 50, "min": 1}), "font_size": ("INT", {"default": 50, "min": 1}),
}, },
} }
@@ -26,12 +26,8 @@ class GridAnnotationNode(BaseNode):
font_size: int, font_size: int,
) -> tuple[Annotation]: ) -> tuple[Annotation]:
font = ImageFont.truetype(str(STATIC_PATH / "Roboto-Regular.ttf"), size=font_size) font = ImageFont.truetype(str(STATIC_PATH / "Roboto-Regular.ttf"), size=font_size)
column_texts_list = self._set_value_to_texts_list( column_texts_list = self._get_texts_from_string(column_texts)
self._get_texts_from_string(column_texts), row_texts_list = self._get_texts_from_string(row_texts)
)
row_texts_list = self._set_value_to_texts_list(
self._get_texts_from_string(row_texts),
)
result = Annotation(column_texts=column_texts_list, row_texts=row_texts_list, font=font) result = Annotation(column_texts=column_texts_list, row_texts=row_texts_list, font=font)
return (result,) return (result,)
@@ -42,8 +38,3 @@ class GridAnnotationNode(BaseNode):
for i in string.split(";") for i in string.split(";")
if (result := i.strip()) != "" if (result := i.strip()) != ""
] ]
def _set_value_to_texts_list(self, texts_list: list[str]) -> list[str]:
if not texts_list:
return ["None"]
return texts_list
+1 -1
View File
@@ -6,7 +6,7 @@ from ..base import BaseNode
class ImageCombineNode(BaseNode): class ImageCombineNode(BaseNode):
RETURN_TYPES: tuple[str] = ("IMAGE",) RETURN_TYPES: tuple[str, ...] = ("IMAGE",)
@classmethod @classmethod
def INPUT_TYPES(cls) -> dict[str, t.Any]: def INPUT_TYPES(cls) -> dict[str, t.Any]:
+1 -1
View File
@@ -12,7 +12,7 @@ from ..utils import (
) )
class BaseImagesGridNode(BaseNode): class BaseImagesGridNode(BaseNode):
RETURN_TYPES: tuple[str] = ("IMAGE",) RETURN_TYPES: tuple[str, ...] = ("IMAGE",)
@classmethod @classmethod
def _create_input_types(cls, coordinate_name: str) -> dict[str, t.Any]: def _create_input_types(cls, coordinate_name: str) -> dict[str, t.Any]:
+1 -1
View File
@@ -6,7 +6,7 @@ from ..base import BaseNode
class LatentCombineNode(BaseNode): class LatentCombineNode(BaseNode):
RETURN_TYPES: tuple[str] = ("LATENT",) RETURN_TYPES: tuple[str, ...] = ("LATENT",)
@classmethod @classmethod
def INPUT_TYPES(cls) -> dict[str, t.Any]: def INPUT_TYPES(cls) -> dict[str, t.Any]:
+34 -19
View File
@@ -5,6 +5,9 @@ from contextlib import suppress
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
WIDEST_LETTER = "W"
@dataclass @dataclass
class Annotation(): class Annotation():
column_texts: list[str] column_texts: list[str]
@@ -12,13 +15,6 @@ class Annotation():
font: ImageFont.FreeTypeFont font: ImageFont.FreeTypeFont
@dataclass
class _GridInfo():
image: Image.Image
gap: int
one_image_size: tuple[int, int]
def create_images_grid_by_columns( def create_images_grid_by_columns(
images: list[Image.Image], images: list[Image.Image],
gap: int, gap: int,
@@ -39,6 +35,13 @@ def create_images_grid_by_rows(
return _create_images_grid(images, gap, max_columns, max_rows, annotation) return _create_images_grid(images, gap, max_columns, max_rows, annotation)
@dataclass
class _GridInfo():
image: Image.Image
gap: int
one_image_size: tuple[int, int]
def _create_images_grid( def _create_images_grid(
images: list[Image.Image], images: list[Image.Image],
gap: int, gap: int,
@@ -56,7 +59,7 @@ def _create_images_grid(
if annotation is None: if annotation is None:
return grid_image return grid_image
return _create_grid_annotations( return _create_grid_annotation(
grid_info=_GridInfo( grid_info=_GridInfo(
image=grid_image, image=grid_image,
gap=gap, gap=gap,
@@ -77,27 +80,36 @@ def _arrange_images_on_grid(
gap: int, gap: int,
): ):
for i, image in enumerate(images): for i, image in enumerate(images):
if image.size != size:
image = image.crop((0, 0, *size))
x = (i % max_columns) * (size[0] + gap) x = (i % max_columns) * (size[0] + gap)
y = (i // max_columns) * (size[1] + gap) y = (i // max_columns) * (size[1] + gap)
grid_image.paste(image, (x, y)) grid_image.paste(image, (x, y))
def _create_grid_annotations( def _create_grid_annotation(
grid_info: _GridInfo, grid_info: _GridInfo,
column_texts, column_texts: list[str],
row_texts, row_texts: list[str],
font: ImageFont.FreeTypeFont, font: ImageFont.FreeTypeFont,
) -> Image.Image: ) -> Image.Image:
if not column_texts or not row_texts: if not column_texts and not row_texts:
raise ValueError("Column text or row text is empty") raise ValueError("Column text and row text is empty")
grid = grid_info.image grid = grid_info.image
margin = font.size // 2 left_padding = 0
left_padding = int(max(map(font.getlength, row_texts))) + 2*margin top_padding = 0
top_padding = font.size + 2*margin
if row_texts:
left_padding = int(
max(
font.getlength(splitted_text)
for raw_text in row_texts
for splitted_text in raw_text.split("\n")
)
+ font.getlength(WIDEST_LETTER)*2
)
if column_texts:
top_padding = max(elem.count("\n") for elem in column_texts) * int(font.size) + int(font.size * 2)
image = Image.new( image = Image.new(
"RGB", "RGB",
@@ -105,9 +117,11 @@ def _create_grid_annotations(
color="white", color="white",
) )
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
# https://github.com/python-pillow/Pillow/blob/9.5.x/docs/reference/ImageDraw.rst
draw.font = font # type: ignore draw.font = font # type: ignore
_paste_image_to_lower_left_corner(image, grid) _paste_image_to_lower_left_corner(image, grid)
if column_texts:
_draw_column_text( _draw_column_text(
draw=draw, draw=draw,
texts=column_texts, texts=column_texts,
@@ -115,6 +129,7 @@ def _create_grid_annotations(
left_padding=left_padding, left_padding=left_padding,
top_padding=top_padding, top_padding=top_padding,
) )
if row_texts:
_draw_row_text( _draw_row_text(
draw=draw, draw=draw,
texts=row_texts, texts=row_texts,
@@ -181,7 +196,7 @@ def _draw_center_text(
fill: t.Any = "black", fill: t.Any = "black",
) -> None: ) -> None:
_, _, *text_size = draw.textbbox((0, 0), text) _, _, *text_size = draw.textbbox((0, 0), text)
draw.text( draw.multiline_text(
( (
(xy[2] - text_size[0] + xy[0]) / 2, (xy[2] - text_size[0] + xy[0]) / 2,
(xy[3] - text_size[1] + xy[1]) / 2, (xy[3] - text_size[1] + xy[1]) / 2,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

After

Width:  |  Height:  |  Size: 296 KiB

+473
View File
@@ -0,0 +1,473 @@
{
"last_node_id": 19,
"last_link_id": 36,
"nodes": [
{
"id": 6,
"type": "VAEDecode",
"pos": [
1083,
263
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"pinned": true,
"collapsed": true
},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 11,
"slot_index": 0
},
{
"name": "vae",
"type": "VAE",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
29
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAE Decode"
}
},
{
"id": 3,
"type": "XY Plot",
"pos": [
529,
778
],
"size": {
"0": 225.0937042236328,
"1": 244
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "script",
"type": "SCRIPT",
"links": [
23
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "XY Plot"
},
"widgets_values": [
"Steps",
"20;30;40",
"CFG Scale",
"7;9;11",
10,
"False",
0,
"____________EXAMPLES____________\n(X/Y_types) (X/Y_values)\nLatent Batch n/a\nSeeds++ Batch 3\nSteps 15;20;25\nCFG Scale 5;10;15;20\nSampler(1) dpmpp_2s_ancestral;euler;ddim\nSampler(2) dpmpp_2m,karras;heun,normal\nDenoise .3;.4;.5;.6;.7\nVAE vae_1; vae_2; vae_3\n\n____________SAMPLERS____________\neuler;\neuler_ancestral;\nheun;\ndpm_2;\ndpm_2_ancestral;\nlms;\ndpm_fast;\ndpm_adaptive;\ndpmpp_2s_ancestral;\ndpmpp_sde;\ndpmpp_2m;\nddim;\nuni_pc;\nuni_pc_bh2\n\n___________SCHEDULERS___________\nkarras;\nnormal;\nsimple;\nddim_uniform\n\n______________VAE_______________\nkl-f8-anime2.ckpt;\nnovelai.vae.pt;\nvae-ft-mse-840000-ema-pruned.ckpt\n\n_____________NOTES______________\n- During a 'Latent Batch', the corresponding X/Y_value is ignored.\n- During a 'Latent Batch', the latent_id is ignored.\n- For a 'Seeds++ Batch', starting seed is defined by the KSampler.\n- Trailing semicolons are ignored in the X/Y_values.\n- Parameter types not set by this node are defined in the KSampler."
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 2,
"type": "KSampler (Efficient)",
"pos": [
767,
252
],
"size": {
"0": 288.36614990234375,
"1": 374
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 2
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 3
},
{
"name": "latent_image",
"type": "LATENT",
"link": 4
},
{
"name": "optional_vae",
"type": "VAE",
"link": 5
},
{
"name": "script",
"type": "SCRIPT",
"link": 23,
"slot_index": 5
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": []
},
{
"name": "CONDITIONING+",
"type": "CONDITIONING",
"links": []
},
{
"name": "CONDITIONING-",
"type": "CONDITIONING",
"links": null
},
{
"name": "LATENT",
"type": "LATENT",
"links": [
11
],
"slot_index": 3
},
{
"name": "VAE",
"type": "VAE",
"links": [
12
],
"slot_index": 4
},
{
"name": "IMAGE",
"type": "IMAGE",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler (Efficient)"
},
"widgets_values": [
"Script",
0,
428398671204662,
false,
20,
7,
"dpmpp_2m",
"karras",
1,
"Enabled"
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 1,
"type": "Efficient Loader",
"pos": [
529,
251
],
"size": {
"0": 222.70794677734375,
"1": 490.5440673828125
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CONDITIONING+",
"type": "CONDITIONING",
"links": [
2
],
"slot_index": 1
},
{
"name": "CONDITIONING-",
"type": "CONDITIONING",
"links": [
3
],
"slot_index": 2
},
{
"name": "LATENT",
"type": "LATENT",
"links": [
4
],
"slot_index": 3
},
{
"name": "VAE",
"type": "VAE",
"links": [
5
],
"slot_index": 4
},
{
"name": "CLIP",
"type": "CLIP",
"links": null
}
],
"properties": {
"Node name for S&R": "Efficient Loader"
},
"widgets_values": [
"Meina-v9.safetensors",
"novelai.vae.pt",
-2,
"1girl, (hanfu), sidelighting, wallpaper",
"(worst quality:2, low quality:2), (zombie, sketch, interlocked fingers, comic)",
512,
512,
1
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 5,
"type": "ImagesGridByColumns",
"pos": [
1293,
247
],
"size": [
315,
102
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 29,
"slot_index": 0
},
{
"name": "annotation",
"type": "GRID_ANNOTATION",
"link": 36
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
15
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImagesGridByColumns"
},
"widgets_values": [
5,
3
],
"color": "#322",
"bgcolor": "#533"
},
{
"id": 7,
"type": "PreviewImage",
"pos": [
1290,
388
],
"size": {
"0": 737.5480346679688,
"1": 635.1529541015625
},
"flags": {
"collapsed": false
},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 15
}
],
"properties": {
"Node name for S&R": "Preview Image"
}
},
{
"id": 19,
"type": "GridAnnotation",
"pos": [
1070,
305
],
"size": [
210,
326
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "GRID_ANNOTATION",
"type": "GRID_ANNOTATION",
"links": [
36
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "GridAnnotation"
},
"widgets_values": [
"Steps: 20;Steps: 30;Steps: 40",
"CGF: 7\nQuality: 10/10;\nCGF: 9\nQuality: 9/10;\nCGF: 11\nQuality: 5/10;",
50
],
"color": "#322",
"bgcolor": "#533"
}
],
"links": [
[
1,
1,
0,
2,
0,
"MODEL"
],
[
2,
1,
1,
2,
1,
"CONDITIONING"
],
[
3,
1,
2,
2,
2,
"CONDITIONING"
],
[
4,
1,
3,
2,
3,
"LATENT"
],
[
5,
1,
4,
2,
4,
"VAE"
],
[
11,
2,
3,
6,
0,
"LATENT"
],
[
12,
2,
4,
6,
1,
"VAE"
],
[
15,
5,
0,
7,
0,
"IMAGE"
],
[
23,
3,
0,
2,
5,
"SCRIPT"
],
[
29,
6,
0,
5,
0,
"IMAGE"
],
[
36,
19,
0,
5,
1,
"GRID_ANNOTATION"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 KiB

After

Width:  |  Height:  |  Size: 530 KiB