From b7faaa090b882ee79bd4fb4e08cc3df0c8e2ef0f Mon Sep 17 00:00:00 2001 From: TTPlanetPIG Date: Fri, 3 Jul 2026 23:13:36 +0800 Subject: [PATCH] Feather Smart Tile focus patches without masks --- TTP_toolsets.py | 39 +++++++++++++++++++ scripts/smoke_smart_tile_grid_crop.py | 55 +++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/TTP_toolsets.py b/TTP_toolsets.py index bcbd48d..d9e53c2 100644 --- a/TTP_toolsets.py +++ b/TTP_toolsets.py @@ -749,6 +749,43 @@ def _ttp_tile_object_mask_array(tile, out_width, out_height, mask_blend_mode="ma return array[:, :, None] +def _ttp_should_rect_feather_tile(tile, is_large_tile, mask_blend_mode): + if str(mask_blend_mode) not in ("auto", "mask_feather"): + return False + if is_large_tile or tile.get("object_mask"): + return False + label = str(tile.get("label", "") or "").lower() + name = str(tile.get("name", "") or "").lower() + source = str(tile.get("source", "") or "").lower() + text = " ".join((label, name, source)) + if any(word in text for word in ("background", "context", "full image", "full_image", "large context")): + return False + focus_keywords = ( + "face", "head", "eye", "eyes", "eyelash", "eyebrow", "glasses", + "hand", "hands", "finger", "fingers", "mouth", "lip", "lips", + "teeth", "nose", "ear", "ears", "text", "letter", "logo", + "detail", "focus", "foreground", "object", + ) + if any(keyword in text for keyword in focus_keywords): + return True + if source in ("auto", "paint", "sam", "sam3", "sam3.1", "qwen", "qwenvl"): + return float(tile.get("occlusion_priority", 0.0)) >= 100.0 or float(tile.get("layer", 0.0)) >= 2.0 + edges = tile.get("overlap_edges_px_source", {}) + has_surrounding_overlap = all(float(edges.get(edge, 0.0)) > 0.0 for edge in ("left", "right", "top", "bottom")) + return has_surrounding_overlap and ( + float(tile.get("occlusion_priority", 0.0)) > 0.0 or float(tile.get("layer", 0.0)) > 0.0 + ) + + +def _ttp_rect_feather_mask(width, height, feather): + feather = int(round(float(feather))) + if feather <= 0: + return np.ones((height, width, 1), dtype=np.float32) + edge_limit = max(1, min(int(width), int(height)) // 3) + feather = max(1, min(feather, edge_limit)) + return _ttp_create_feather_mask(int(width), int(height), feather) + + def _ttp_decode_interactive_paint_mask(mask_payload, image_width, image_height): try: payload = json.loads(str(mask_payload or "").strip() or "{}") @@ -3957,6 +3994,8 @@ class TTP_Smart_Tile_Assemble_Experimental: mask = object_mask else: mask = mask * object_mask + elif _ttp_should_rect_feather_tile(tile, is_large_tile, str(mask_blend_mode)): + mask = mask * _ttp_rect_feather_mask(out_w, out_h, blend) coverage_mask = np.clip(mask.copy(), 0.0, 1.0) importance = max(0.0, float(tile.get("importance", 1.0))) priority = max(0.0, float(tile.get("priority", 50.0))) diff --git a/scripts/smoke_smart_tile_grid_crop.py b/scripts/smoke_smart_tile_grid_crop.py index ca6148d..a70c7e2 100644 --- a/scripts/smoke_smart_tile_grid_crop.py +++ b/scripts/smoke_smart_tile_grid_crop.py @@ -1019,6 +1019,61 @@ small_above, _small_above_weights = assemble_node.assemble_tiles( small_above_pixel = small_above.array[0, 4, 4] assert_equal(float(small_above_pixel[2]) > float(small_above_pixel[0]), True, "small_tile_on_top should stack smaller tiles above larger context tiles") +feather_stack_meta = { + "type": "ttp_smart_tile", + "original_size": [16, 16], + "tiles": [ + { + "name": "background", + "label": "background", + "core_box": [0, 0, 16, 16], + "sample_box": [0, 0, 16, 16], + "tile_canvas_size": [16, 16], + "tile_canvas_box": [0, 0, 16, 16], + "overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0}, + "blend": 0, + "importance": 1.0, + "priority": 0.0, + "layer": 0, + "occlusion_priority": 0, + }, + { + "name": "face_detail", + "label": "face detail", + "core_box": [4, 4, 8, 8], + "sample_box": [4, 4, 8, 8], + "tile_canvas_size": [8, 8], + "tile_canvas_box": [0, 0, 8, 8], + "overlap_edges_px_source": {"left": 0, "right": 0, "top": 0, "bottom": 0}, + "blend": 4, + "importance": 1.0, + "priority": 0.0, + "layer": 2, + "occlusion_priority": 1000, + }, + ], +} +feather_tile_set = { + "type": "ttp_smart_tile_set", + "tile_meta": feather_stack_meta, + "tile_images": [ + ttp.pil2tensor(Image.new("RGB", (16, 16), (220, 20, 20)))[0], + ttp.pil2tensor(Image.new("RGB", (8, 8), (20, 20, 220)))[0], + ], +} +feathered_stack, _feathered_weights = assemble_node.assemble_tiles( + blend_multiplier=1.0, + output_scale=1.0, + use_priority=True, + base_canvas_mode="black", + mask_blend_mode="mask_feather", + tile_set=feather_tile_set, +) +feather_edge_pixel = feathered_stack.array[0, 4, 8] +feather_center_pixel = feathered_stack.array[0, 8, 8] +assert_equal(float(feather_edge_pixel[0]) > float(feather_edge_pixel[2]), True, "focus tile fallback feather should keep lower pixels visible at the rectangle edge") +assert_equal(float(feather_center_pixel[2]) > float(feather_center_pixel[0]), True, "focus tile fallback feather should keep the focus tile strong in the center") + deferred_output, deferred_weights = assemble_node.assemble_tiles( blend_multiplier=1.0, output_scale=1.0,