├── .github └── workflows │ └── publish.yml ├── 2024-10-23_10-59-03.png ├── ComfyUI_temp_zbacb_00001_.png ├── EvenImageResizer.py ├── GridImageSplitter.py ├── LICENSE ├── README.md ├── Screenshot_2024-12-13_15-30-41.png ├── __init__.py ├── example.json ├── pyproject.toml └── requirements.txt /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - "pyproject.toml" 10 | 11 | jobs: 12 | publish-node: 13 | name: Publish Custom Node to registry 14 | runs-on: ubuntu-latest 15 | # if this is a forked repository. Skipping the workflow. 16 | if: github.event.repository.fork == false 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v4 20 | - name: Publish Custom Node 21 | uses: Comfy-Org/publish-node-action@main 22 | with: 23 | ## Add your own personal access token to your Github Repository secrets and reference it here. 24 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 25 | -------------------------------------------------------------------------------- /2024-10-23_10-59-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormcenter/ComfyUI-AutoSplitGridImage/a7ec88a33a7d861b998b87668f6e45046b172364/2024-10-23_10-59-03.png -------------------------------------------------------------------------------- /ComfyUI_temp_zbacb_00001_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormcenter/ComfyUI-AutoSplitGridImage/a7ec88a33a7d861b998b87668f6e45046b172364/ComfyUI_temp_zbacb_00001_.png -------------------------------------------------------------------------------- /EvenImageResizer.py: -------------------------------------------------------------------------------- 1 | import torch 2 | 3 | class EvenImageResizer: 4 | @classmethod 5 | def INPUT_TYPES(s): 6 | return { 7 | "required": { 8 | "image": ("IMAGE",), 9 | }, 10 | } 11 | 12 | RETURN_TYPES = ("IMAGE",) 13 | FUNCTION = "resize_to_even" 14 | CATEGORY = "image/processing" 15 | 16 | def resize_to_even(self, image): 17 | # 确保图片是 4D tensor [batch, height, width, channels] 18 | if len(image.shape) == 3: 19 | image = image.unsqueeze(0) 20 | 21 | height, width = image.shape[1:3] 22 | 23 | # 计算新的高度和宽度 24 | new_height = height - (height % 2) 25 | new_width = width - (width % 2) 26 | 27 | # 如果尺寸没有变化,直接返回原图 28 | if new_height == height and new_width == width: 29 | return (image,) 30 | 31 | # 裁剪图片以确保宽高为偶数 32 | resized_image = image[:, :new_height, :new_width, :] 33 | 34 | return (resized_image,) 35 | 36 | NODE_CLASS_MAPPINGS = { 37 | "EvenImageResizer": EvenImageResizer 38 | } -------------------------------------------------------------------------------- /GridImageSplitter.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import cv2 4 | 5 | class GridImageSplitter: 6 | @classmethod 7 | def INPUT_TYPES(s): 8 | return { 9 | "required": { 10 | "image": ("IMAGE",), 11 | "rows": ("INT", {"default": 2, "min": 1, "max": 10}), 12 | "cols": ("INT", {"default": 3, "min": 1, "max": 10}), 13 | "row_split_method": (["uniform", "edge_detection"],), 14 | "col_split_method": (["uniform", "edge_detection"],), 15 | }, 16 | } 17 | 18 | RETURN_TYPES = ("IMAGE", "IMAGE") 19 | FUNCTION = "split_image" 20 | CATEGORY = "image/processing" 21 | 22 | def remove_external_borders(self, img_np): 23 | """ 24 | 处理外部边缘,加强对黑色边框的检测 25 | """ 26 | if img_np.size == 0 or img_np is None: 27 | return img_np 28 | 29 | # 转换为多个色彩空间 30 | gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY) 31 | hsv = cv2.cvtColor(img_np, cv2.COLOR_RGB2HSV) 32 | 33 | height, width = img_np.shape[:2] 34 | 35 | # 最小裁剪量 36 | min_trim = 18 37 | # 检查范围扩大到30像素 38 | check_width = 30 39 | 40 | def is_black_region(region): 41 | """检查区域是否为黑色区域""" 42 | if len(region.shape) == 3: 43 | region_gray = cv2.cvtColor(region, cv2.COLOR_RGB2GRAY) 44 | else: 45 | region_gray = region 46 | 47 | # 计算暗色像素的比例 48 | dark_ratio = np.mean(region_gray < 40) 49 | # 如果超过60%的像素是暗色的,认为是黑边 50 | return dark_ratio > 0.6 51 | 52 | def is_white_region(region): 53 | """检查区域是否为白色区域""" 54 | if len(region.shape) == 3: 55 | region_hsv = cv2.cvtColor(region, cv2.COLOR_RGB2HSV) 56 | sat_mean = np.mean(region_hsv[:,:,1]) 57 | val_mean = np.mean(region_hsv[:,:,2]) 58 | return sat_mean < 30 and val_mean > 225 59 | return False 60 | 61 | def find_border(gray_img, is_vertical=True, from_start=True): 62 | """查找边界""" 63 | if is_vertical: 64 | total_size = width 65 | chunk_size = 5 # 每次检查5个像素 66 | else: 67 | total_size = height 68 | chunk_size = 5 69 | 70 | if from_start: 71 | range_iter = range(0, total_size-chunk_size, chunk_size) 72 | else: 73 | range_iter = range(total_size-chunk_size, 0, -chunk_size) 74 | 75 | for i in range_iter: 76 | if is_vertical: 77 | chunk = img_np[:, i:i+chunk_size] if from_start else img_np[:, i-chunk_size:i] 78 | else: 79 | chunk = img_np[i:i+chunk_size, :] if from_start else img_np[i-chunk_size:i, :] 80 | 81 | # 分别检查黑边和白边 82 | if not (is_black_region(chunk) or is_white_region(chunk)): 83 | return i if from_start else i 84 | 85 | return min_trim if from_start else total_size - min_trim 86 | 87 | # 检测左边界 88 | left = find_border(gray, is_vertical=True, from_start=True) 89 | 90 | # 检测右边界 91 | right = find_border(gray, is_vertical=True, from_start=False) 92 | 93 | # 检测上边界 94 | top = find_border(gray, is_vertical=False, from_start=True) 95 | 96 | # 检测下边界 97 | bottom = find_border(gray, is_vertical=False, from_start=False) 98 | 99 | # 强制应用最小裁剪 100 | # 检查左侧边缘 101 | left_region = gray[:, :check_width] 102 | if np.mean(left_region < 40) > 0.3: # 如果有超过30%的暗色像素 103 | left = max(left, min_trim) 104 | 105 | # 检查右侧边缘 106 | right_region = gray[:, -check_width:] 107 | if np.mean(right_region < 40) > 0.3: # 如果有超过30%的暗色像素 108 | right = min(right, width - min_trim) 109 | 110 | # 检查上边缘 111 | top_region = gray[:check_width, :] 112 | if np.mean(top_region < 40) > 0.3: 113 | top = max(top, min_trim) 114 | 115 | # 检查下边缘 116 | bottom_region = gray[-check_width:, :] 117 | if np.mean(bottom_region < 40) > 0.3: 118 | bottom = min(bottom, height - min_trim) 119 | 120 | # 确保裁剪合理 121 | if (right - left) < width * 0.5 or (bottom - top) < height * 0.5: 122 | return img_np 123 | 124 | # 应用裁剪 125 | cropped = img_np[top:bottom, left:right] 126 | 127 | # 进行二次检查,确保没有遗漏的黑边 128 | if cropped.shape[1] > 2 * min_trim: 129 | right_edge = cv2.cvtColor(cropped[:, -min_trim:], cv2.COLOR_RGB2GRAY) 130 | if np.mean(right_edge < 40) > 0.3: 131 | cropped = cropped[:, :-min_trim] 132 | 133 | return cropped 134 | 135 | def detect_split_borders(self, img_strip, is_vertical=True): 136 | """ 137 | 检测分割线区域的边框 138 | """ 139 | hsv = cv2.cvtColor(img_strip, cv2.COLOR_RGB2HSV) 140 | lab = cv2.cvtColor(img_strip, cv2.COLOR_RGB2LAB) 141 | 142 | sat = hsv[:, :, 1] 143 | val = hsv[:, :, 2] 144 | l_channel = lab[:, :, 0] 145 | 146 | # 检测白色和黑色区域 147 | is_border = ((sat < 10) & (val > 248)) | (l_channel < 30) 148 | 149 | if is_vertical: 150 | border_ratios = np.mean(is_border, axis=1) 151 | indices = np.where(border_ratios < 0.95)[0] 152 | else: 153 | border_ratios = np.mean(is_border, axis=0) 154 | indices = np.where(border_ratios < 0.95)[0] 155 | 156 | if len(indices) == 0: 157 | return 0, img_strip.shape[1] if is_vertical else img_strip.shape[0] 158 | 159 | return indices[0], indices[-1] 160 | 161 | def adjust_split_line(self, img_np, split_pos, is_vertical=True, margin=15): 162 | """ 163 | 调整分割线附近的边界 164 | """ 165 | height, width = img_np.shape[:2] 166 | 167 | if is_vertical: 168 | left_bound = max(0, split_pos - margin) 169 | right_bound = min(width, split_pos + margin) 170 | strip = img_np[:, left_bound:right_bound] 171 | start, end = self.detect_split_borders(strip, False) 172 | 173 | start = max(0, start - 5) 174 | end = min(strip.shape[1], end + 5) 175 | 176 | return left_bound + start, left_bound + end 177 | else: 178 | top_bound = max(0, split_pos - margin) 179 | bottom_bound = min(height, split_pos + margin) 180 | strip = img_np[top_bound:bottom_bound, :] 181 | start, end = self.detect_split_borders(strip, True) 182 | 183 | start = max(0, start - 5) 184 | end = min(strip.shape[0], end + 5) 185 | 186 | return top_bound + start, top_bound + end 187 | 188 | def find_split_positions(self, image, num_splits, is_vertical, split_method): 189 | if split_method == "uniform": 190 | size = image.shape[1] if is_vertical else image.shape[0] 191 | return [i * size // (num_splits + 1) for i in range(1, num_splits + 1)] 192 | else: 193 | gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) 194 | edges = cv2.Canny(gray, 50, 150) 195 | edge_density = np.sum(edges, axis=0) if is_vertical else np.sum(edges, axis=1) 196 | 197 | window_size = len(edge_density) // (num_splits + 1) // 2 198 | smoothed_density = np.convolve(edge_density, np.ones(window_size)/window_size, mode='same') 199 | 200 | split_positions = [] 201 | for i in range(1, num_splits + 1): 202 | start = i * len(smoothed_density) // (num_splits + 1) - window_size 203 | end = i * len(smoothed_density) // (num_splits + 1) + window_size 204 | split = start + np.argmin(smoothed_density[start:end]) 205 | split_positions.append(split) 206 | return split_positions 207 | 208 | def split_image(self, image, rows, cols, row_split_method, col_split_method): 209 | if len(image.shape) == 3: 210 | image = image.unsqueeze(0) 211 | 212 | img_np = (image[0].cpu().numpy() * 255).astype(np.uint8) 213 | height, width = img_np.shape[:2] 214 | 215 | # 获取分割位置 216 | vertical_splits = self.find_split_positions(img_np, cols - 1, True, col_split_method) 217 | horizontal_splits = self.find_split_positions(img_np, rows - 1, False, row_split_method) 218 | 219 | # 创建预览图 220 | preview_img = image.clone() 221 | green_line = torch.tensor([0.0, 1.0, 0.0]).view(1, 1, 1, 3) 222 | for x in vertical_splits: 223 | preview_img[:, :, x:x+2, :] = green_line 224 | for y in horizontal_splits: 225 | preview_img[:, y:y+2, :, :] = green_line 226 | 227 | # 调整分割位置 228 | adjusted_v_splits = [] 229 | for split in vertical_splits: 230 | left, right = self.adjust_split_line(img_np, split, True) 231 | adjusted_v_splits.extend([left, right]) 232 | 233 | adjusted_h_splits = [] 234 | for split in horizontal_splits: 235 | top, bottom = self.adjust_split_line(img_np, split, False) 236 | adjusted_h_splits.extend([top, bottom]) 237 | 238 | # 获取分割边界 239 | h_splits = [0] + sorted(adjusted_h_splits) + [height] 240 | v_splits = [0] + sorted(adjusted_v_splits) + [width] 241 | 242 | # 处理所有分割区域 243 | split_images = [] 244 | max_h = 0 245 | max_w = 0 246 | 247 | # 第一次遍历: 找出所有裁剪后图片的最大宽度和高度 248 | temp_splits = [] 249 | for i in range(0, len(h_splits)-1, 2): 250 | for j in range(0, len(v_splits)-1, 2): 251 | top = h_splits[i] 252 | bottom = h_splits[i+1] 253 | left = v_splits[j] 254 | right = v_splits[j+1] 255 | 256 | cell_np = img_np[top:bottom, left:right] 257 | trimmed_cell = self.remove_external_borders(cell_np) 258 | temp_splits.append(trimmed_cell) 259 | 260 | h, w = trimmed_cell.shape[:2] 261 | max_h = max(max_h, h) 262 | max_w = max(max_w, w) 263 | 264 | # 第二次遍历: 将所有图片调整为相同尺寸,保持原始比例 265 | for cell_np in temp_splits: 266 | h, w = cell_np.shape[:2] 267 | # 计算缩放比例 268 | scale = min(max_h/h, max_w/w) 269 | new_h = int(h * scale) 270 | new_w = int(w * scale) 271 | 272 | # 居中放置 273 | resized = cv2.resize(cell_np, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) 274 | canvas = np.zeros((max_h, max_w, 3), dtype=np.uint8) 275 | y_offset = (max_h - new_h) // 2 276 | x_offset = (max_w - new_w) // 2 277 | canvas[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized 278 | 279 | # 转换为tensor 280 | cell_tensor = torch.from_numpy(canvas).float() / 255.0 281 | cell_tensor = cell_tensor.unsqueeze(0) 282 | split_images.append(cell_tensor) 283 | 284 | stacked_images = torch.cat(split_images, dim=0) 285 | 286 | if stacked_images.shape[-1] != 3: 287 | stacked_images = stacked_images.permute(0, 2, 3, 1) 288 | 289 | print(f"Final stacked shape: {stacked_images.shape}") 290 | 291 | return (preview_img, stacked_images) 292 | 293 | NODE_CLASS_MAPPINGS = { 294 | "GridImageSplitter": GridImageSplitter 295 | } 296 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ComfyUI-AutoSplitGridImage 2 | 3 | ComfyUI-AutoSplitGridImage is a custom node for ComfyUI that provides flexible image splitting functionality. It allows users to choose between edge detection and uniform division for both row and column splits, offering a customizable approach to grid-based image segmentation. 4 | 5 | ![example_workflow](./2024-10-23_10-59-03.png) 6 | 7 | ![example_png](./ComfyUI_temp_zbacb_00001_.png) 8 | 9 | ![example_png](./Screenshot_2024-12-13_15-30-41.png) 10 | 11 | ## Features 12 | 13 | - Customizable splitting methods for both rows and columns 14 | - Choice between edge detection and uniform division for each axis 15 | - Adjustable number of rows and columns 16 | - Preview image with grid lines 17 | - Outputs both the preview image and individual grid cells 18 | - Automatic even dimension resizing with EvenImageResizer node 19 | 20 | ## Installation 21 | 22 | 1. Clone this repository into your ComfyUI `custom_nodes` directory: 23 | ``` 24 | cd /path/to/ComfyUI/custom_nodes 25 | git clone https://github.com/yourusername/ComfyUI-AutoSplitGridImage.git 26 | ``` 27 | 2. Restart ComfyUI or reload custom nodes. 28 | 29 | ## Usage 30 | 31 | ### GridImageSplitter Node 32 | 1. In the ComfyUI interface, find the "GridImageSplitter" node under the "image/processing" category. 33 | 2. Connect an image output to the "image" input of the GridImageSplitter node. 34 | 3. Set the desired number of rows and columns. 35 | 4. Choose the splitting method for rows and columns (uniform or edge detection). 36 | 5. The node will output two images: 37 | - A preview image showing the grid lines 38 | - A tensor containing all the split image cells 39 | 40 | ### EvenImageResizer Node 41 | 1. Find the "EvenImageResizer" node under the "image/processing" category. 42 | 2. Connect an image output to the "image" input. 43 | 3. The node will automatically ensure the output image has even dimensions by trimming if necessary. 44 | 45 | ## Parameters 46 | 47 | ### GridImageSplitter 48 | - `image`: Input image to be split 49 | - `rows`: Number of rows to split the image into (default: 2, range: 1-10) 50 | - `cols`: Number of columns to split the image into (default: 3, range: 1-10) 51 | - `row_split_method`: Method to split rows ("uniform" or "edge_detection") 52 | - `col_split_method`: Method to split columns ("uniform" or "edge_detection") 53 | 54 | ### EvenImageResizer 55 | - `image`: Input image to be processed 56 | 57 | ## How It Works 58 | 59 | - Uniform Splitting: Divides the image into equal parts along the specified axis. 60 | - Edge Detection Splitting: Uses OpenCV's Canny edge detection to find natural splitting points in the image. 61 | - Even Dimension Resizing: Automatically trims images to ensure both width and height are even numbers. 62 | 63 | ## Contributing 64 | 65 | Contributions are welcome! Please feel free to submit a Pull Request. 66 | 67 | ## License 68 | 69 | This project is licensed under the Apache License 2.0 License - see the [LICENSE](LICENSE) file for details. 70 | 71 | ## Acknowledgements 72 | 73 | - This project is designed to work with [ComfyUI](https://github.com/comfyanonymous/ComfyUI). 74 | - Edge detection is implemented using OpenCV. 75 | -------------------------------------------------------------------------------- /Screenshot_2024-12-13_15-30-41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormcenter/ComfyUI-AutoSplitGridImage/a7ec88a33a7d861b998b87668f6e45046b172364/Screenshot_2024-12-13_15-30-41.png -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .GridImageSplitter import GridImageSplitter 2 | from .EvenImageResizer import EvenImageResizer, NODE_CLASS_MAPPINGS as EVEN_RESIZER_NODE_CLASS_MAPPINGS 3 | 4 | __all__ = ['GridImageSplitter', 'EvenImageResizer'] 5 | 6 | NODE_CLASS_MAPPINGS = { 7 | "GridImageSplitter": GridImageSplitter, 8 | "EvenImageResizer": EvenImageResizer 9 | } -------------------------------------------------------------------------------- /example.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 7, 3 | "last_link_id": 3, 4 | "nodes": [ 5 | { 6 | "id": 4, 7 | "type": "LoadImage", 8 | "pos": { 9 | "0": 970, 10 | "1": 456, 11 | "2": 0, 12 | "3": 0, 13 | "4": 0, 14 | "5": 0, 15 | "6": 0, 16 | "7": 0, 17 | "8": 0, 18 | "9": 0 19 | }, 20 | "size": { 21 | "0": 315, 22 | "1": 314 23 | }, 24 | "flags": {}, 25 | "order": 0, 26 | "mode": 0, 27 | "inputs": [], 28 | "outputs": [ 29 | { 30 | "name": "IMAGE", 31 | "type": "IMAGE", 32 | "links": [ 33 | 1 34 | ], 35 | "slot_index": 0, 36 | "shape": 3 37 | }, 38 | { 39 | "name": "MASK", 40 | "type": "MASK", 41 | "links": null, 42 | "shape": 3 43 | } 44 | ], 45 | "properties": { 46 | "Node name for S&R": "LoadImage" 47 | }, 48 | "widgets_values": [ 49 | "b6eea9a635e147bdbec1d2ebfa22c6e0 (1).png", 50 | "image" 51 | ] 52 | }, 53 | { 54 | "id": 5, 55 | "type": "PreviewImage", 56 | "pos": { 57 | "0": 1920, 58 | "1": 199, 59 | "2": 0, 60 | "3": 0, 61 | "4": 0, 62 | "5": 0, 63 | "6": 0, 64 | "7": 0, 65 | "8": 0, 66 | "9": 0 67 | }, 68 | "size": { 69 | "0": 210, 70 | "1": 246 71 | }, 72 | "flags": {}, 73 | "order": 2, 74 | "mode": 0, 75 | "inputs": [ 76 | { 77 | "name": "images", 78 | "type": "IMAGE", 79 | "link": 2 80 | } 81 | ], 82 | "outputs": [], 83 | "properties": { 84 | "Node name for S&R": "PreviewImage" 85 | } 86 | }, 87 | { 88 | "id": 3, 89 | "type": "GridImageSplitter", 90 | "pos": { 91 | "0": 1371, 92 | "1": 491, 93 | "2": 0, 94 | "3": 0, 95 | "4": 0, 96 | "5": 0, 97 | "6": 0, 98 | "7": 0, 99 | "8": 0, 100 | "9": 0 101 | }, 102 | "size": { 103 | "0": 315, 104 | "1": 150 105 | }, 106 | "flags": {}, 107 | "order": 1, 108 | "mode": 0, 109 | "inputs": [ 110 | { 111 | "name": "image", 112 | "type": "IMAGE", 113 | "link": 1 114 | } 115 | ], 116 | "outputs": [ 117 | { 118 | "name": "IMAGE", 119 | "type": "IMAGE", 120 | "links": [ 121 | 2 122 | ], 123 | "slot_index": 0, 124 | "shape": 3 125 | }, 126 | { 127 | "name": "IMAGE", 128 | "type": "IMAGE", 129 | "links": [ 130 | 3 131 | ], 132 | "slot_index": 1, 133 | "shape": 3 134 | } 135 | ], 136 | "properties": { 137 | "Node name for S&R": "GridImageSplitter" 138 | }, 139 | "widgets_values": [ 140 | 2, 141 | 3 142 | ] 143 | }, 144 | { 145 | "id": 6, 146 | "type": "PreviewImage", 147 | "pos": { 148 | "0": 1923, 149 | "1": 715, 150 | "2": 0, 151 | "3": 0, 152 | "4": 0, 153 | "5": 0, 154 | "6": 0, 155 | "7": 0, 156 | "8": 0, 157 | "9": 0 158 | }, 159 | "size": { 160 | "0": 210, 161 | "1": 246 162 | }, 163 | "flags": {}, 164 | "order": 3, 165 | "mode": 0, 166 | "inputs": [ 167 | { 168 | "name": "images", 169 | "type": "IMAGE", 170 | "link": 3 171 | } 172 | ], 173 | "outputs": [], 174 | "properties": { 175 | "Node name for S&R": "PreviewImage" 176 | } 177 | } 178 | ], 179 | "links": [ 180 | [ 181 | 1, 182 | 4, 183 | 0, 184 | 3, 185 | 0, 186 | "IMAGE" 187 | ], 188 | [ 189 | 2, 190 | 3, 191 | 0, 192 | 5, 193 | 0, 194 | "IMAGE" 195 | ], 196 | [ 197 | 3, 198 | 3, 199 | 1, 200 | 6, 201 | 0, 202 | "IMAGE" 203 | ] 204 | ], 205 | "groups": [], 206 | "config": {}, 207 | "extra": { 208 | "ds": { 209 | "scale": 0.7247295000000008, 210 | "offset": [ 211 | -224.91286373867808, 212 | -64.33740337427317 213 | ] 214 | } 215 | }, 216 | "version": 0.4 217 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "comfyui-autosplitgridimage" 3 | description = "ComfyUI-AutoSplitGridImage is a custom node for ComfyUI that provides intelligent image splitting functionality. It combines edge detection for column splits and uniform division for row splits, offering a balanced approach to grid-based image segmentation." 4 | version = "1.0.0" 5 | license = {file = "LICENSE"} 6 | dependencies = ["numpy>=1.21.0", "opencv-python>=4.5.0", "pillow>=8.0.0", "torch>=1.7.0"] 7 | 8 | [project.urls] 9 | Repository = "https://github.com/stormcenter/ComfyUI-AutoSplitGridImage" 10 | # Used by Comfy Registry https://comfyregistry.org 11 | 12 | [tool.comfy] 13 | PublisherId = "" 14 | DisplayName = "ComfyUI-AutoSplitGridImage" 15 | Icon = "" 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.21.0 2 | opencv-python>=4.5.0 3 | pillow>=8.0.0 4 | torch>=1.7.0 --------------------------------------------------------------------------------