MaskCropMaster commited on
Commit
bfbe8cd
·
0 Parent(s):

Initial release: MASK CROP MASTER - always-square mask crop for ComfyUI

Browse files
Files changed (2) hide show
  1. README.md +71 -0
  2. __init__.py +86 -0
README.md ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MASK CROP MASTER — ComfyUI Custom Node
2
+
3
+ **Always-square mask crop, even at image edges.**
4
+
5
+ ## The Problem
6
+
7
+ When you mask a region at the **edge** of an image, the standard `Mask Crop Region` node from was-ns produces a **rectangular** crop instead of a square. This happens because it clamps the crop dimensions when the square extends beyond the image bounds.
8
+
9
+ ## The Solution
10
+
11
+ `MASK CROP MASTER` **repositions** the square instead of clamping it. When the crop would extend past an image edge, it slides the square inward so it always fits completely inside the image.
12
+
13
+ ```
14
+ was-ns (rectangle): MASK CROP MASTER (square):
15
+ ┌────────────────────┐ ┌────────────────────┐
16
+ │████████████░░░░░░░░│ │░░░░░░░░░░░░░░░░░░░░│
17
+ │████████████░░░░░░░░│ │░░████████░░░░░░░░░░│
18
+ │░░░░░░░░░░░░░░░░░░░░│ │░░████████░░░░░░░░░░│
19
+ │░░░░░░░░░░░░░░░░░░░░│ │░░░░░░░░░░░░░░░░░░░░│
20
+ └────────────────────┘ └────────────────────┘
21
+ 400×200 (broken) 400×400 (correct)
22
+ ```
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ cd ComfyUI/custom_nodes
28
+ git clone https://huggingface.co/YOUR_USERNAME/MaskCropMaster.git
29
+ ```
30
+
31
+ Restart ComfyUI after cloning.
32
+
33
+ ## Usage
34
+
35
+ Drop-in replacement for `Mask Crop Region` from was-ns:
36
+
37
+ 1. **Remove** the old `Mask Crop Region` node from your workflow
38
+ 2. **Add** `MASK CROP MASTER` from the `WAS Suite/Image/Masking` category
39
+ 3. **Connect** the same inputs: `mask`, `padding`, `region_type`
40
+ 4. **Connect** the same outputs: `cropped_mask`, `crop_data`, coordinates, dimensions
41
+
42
+ ## Inputs
43
+
44
+ | Input | Type | Default | Description |
45
+ |-------|------|---------|-------------|
46
+ | `mask` | MASK | — | Input mask |
47
+ | `padding` | INT | 24 | Padding around the bounding box before squaring |
48
+ | `region_type` | COMBO | "dominant" | "dominant" or "minority" (was-ns compatible) |
49
+
50
+ ## Outputs
51
+
52
+ | Output | Type | Description |
53
+ |--------|------|-------------|
54
+ | `cropped_mask` | MASK | Square-cropped mask |
55
+ | `crop_data` | CROP_DATA | Compatible with `Mask Paste Region` |
56
+ | `top_int` | INT | Y position of the square |
57
+ | `left_int` | INT | X position of the square |
58
+ | `right_int` | INT | Bottom edge |
59
+ | `bottom_int` | INT | Right edge |
60
+ | `width_int` | INT | Width (= side length) |
61
+ | `height_int` | INT | Height (= side length) |
62
+
63
+ ## Compatibility
64
+
65
+ - Works with `Mask Paste Region` from was-ns (same `crop_data` format)
66
+ - Same output names → minimal rewiring needed
67
+ - Tested with ComfyUI latest version
68
+
69
+ ## License
70
+
71
+ Apache 2.0
__init__.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from PIL import Image, ImageOps
4
+
5
+
6
+ class MaskCropMaster:
7
+ """Mask Crop Region replacement that ALWAYS outputs a square crop.
8
+
9
+ Based on WAS_Mask_Crop_Region from was-ns, with the key fix:
10
+ when the square extends beyond the image bounds, it REPOSITIONS
11
+ the crop instead of clamping dimensions (which produces a rectangle).
12
+
13
+ Compatible with Mask Paste Region (same crop_data format).
14
+ """
15
+
16
+ @classmethod
17
+ def INPUT_TYPES(cls):
18
+ return {
19
+ "required": {
20
+ "mask": ("MASK",),
21
+ "padding": ("INT", {"default": 24, "min": 0, "max": 4096, "step": 1}),
22
+ "region_type": (["dominant", "minority"],),
23
+ }
24
+ }
25
+
26
+ RETURN_TYPES = ("MASK", "CROP_DATA", "INT", "INT", "INT", "INT", "INT", "INT")
27
+ RETURN_NAMES = ("cropped_mask", "crop_data", "top_int", "left_int", "right_int", "bottom_int", "width_int", "height_int")
28
+ FUNCTION = "mask_crop_master"
29
+ CATEGORY = "WAS Suite/Image/Masking"
30
+ DESCRIPTION = "Mask Crop Region that always outputs a square, even at image edges."
31
+
32
+ def mask_crop_master(self, mask, padding=24, region_type="dominant"):
33
+ mask_np = mask.cpu().squeeze().numpy()
34
+ mask_pil = Image.fromarray(np.clip(255.0 * mask_np, 0, 255).astype(np.uint8))
35
+
36
+ img_w, img_h = mask_pil.size
37
+
38
+ bbox = mask_pil.getbbox()
39
+ if bbox is None:
40
+ empty = Image.new("L", (img_w, img_h), 0)
41
+ empty_tensor = torch.from_numpy(np.array(empty).astype(np.float32) / 255.0).unsqueeze(0).unsqueeze(1)
42
+ crop_data = ((img_w, img_h), (0, 0, 0, 0))
43
+ return (empty_tensor, crop_data, 0, 0, 0, 0, img_w, img_h)
44
+
45
+ bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox
46
+ bbox_w = bbox_x2 - bbox_x1
47
+ bbox_h = bbox_y2 - bbox_y1
48
+
49
+ side = max(bbox_w, bbox_h) + 2 * padding
50
+ side = min(side, img_w, img_h)
51
+
52
+ cx = (bbox_x1 + bbox_x2) / 2.0
53
+ cy = (bbox_y1 + bbox_y2) / 2.0
54
+
55
+ crop_x = round(cx - side / 2.0)
56
+ crop_y = round(cy - side / 2.0)
57
+
58
+ if crop_x < 0:
59
+ crop_x = 0
60
+ if crop_y < 0:
61
+ crop_y = 0
62
+ if crop_x + side > img_w:
63
+ crop_x = img_w - side
64
+ if crop_y + side > img_h:
65
+ crop_y = img_h - side
66
+
67
+ crop_x2 = crop_x + side
68
+ crop_y2 = crop_y + side
69
+
70
+ cropped_mask = mask_pil.crop((crop_x, crop_y, crop_x2, crop_y2))
71
+
72
+ region_tensor = torch.from_numpy(
73
+ np.array(cropped_mask).astype(np.float32) / 255.0
74
+ ).unsqueeze(0).unsqueeze(1)
75
+
76
+ crop_data = (cropped_mask.size, (crop_x, crop_y, crop_x2, crop_y2))
77
+
78
+ return (region_tensor, crop_data, crop_y, crop_x, crop_y2, crop_x2, side, side)
79
+
80
+
81
+ NODE_CLASS_MAPPINGS = {
82
+ "MaskCropMaster": MaskCropMaster,
83
+ }
84
+ NODE_DISPLAY_NAME_MAPPINGS = {
85
+ "MaskCropMaster": "MASK CROP MASTER",
86
+ }