From e97292df8dd3e7b7c8d8fce104c202dc418d2c3c Mon Sep 17 00:00:00 2001 From: nomcycle Date: Mon, 29 Jul 2024 12:29:15 -0600 Subject: [PATCH 1/4] Created a new node that can dynamically determine the font size to fit text in a given area width and height. --- text.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/text.py b/text.py index 132ce03..db3977a 100644 --- a/text.py +++ b/text.py @@ -3,6 +3,55 @@ from nodes import MAX_RESOLUTION import torchvision.transforms.v2 as T +def calculate_metrics (text, font): + lines = text.split("\n") + # Calculate the width and height of the text + text_width = max(font.getbbox(line)[2] for line in lines) + line_height = font.getmask(text).getbbox()[3] + font.getmetrics()[1] # add descent to height + text_height = line_height * len(lines) + return lines, text_width, text_height, line_height + +def get_fonts (): + return sorted([f for f in os.listdir(FONTS_DIR) if f.endswith('.ttf') or f.endswith('.otf')]) + +class DetectFontSize: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "text": ("STRING", { "multiline": True, "dynamicPrompts": True, "default": "Hello, World!" }), + "font": (get_fonts(), ), + "area_width": ("INT", { "default": 0, "min": 0, "max": 4096, "step": 1 }), + "area_height": ("INT", { "default": 0, "min": 0, "max": 4096, "step": 1 }), + } + } + + RETURN_TYPES = ("INT", "STRING",) + FUNCTION = "execute" + CATEGORY = "essentials/text" + + def get_text_size (self, text, font, size): + from PIL import ImageFont + font = ImageFont.truetype(os.path.join(FONTS_DIR, font), size) + lines, text_width, text_height, line_height = calculate_metrics(text, font) + return text_width, text_height + + def execute(self, text, font, area_width, area_height): + font_size_a, font_size_b = 10, 20 + text_width_a, text_height_a = self.get_text_size(text, font, font_size_a) + text_width_b, text_height_b = self.get_text_size(text, font, font_size_b) + + delta_text_size = max(text_width_b - text_width_a, text_height_b - text_height_a) + + min_text_size = min(text_height_a, text_width_a) + min_area_size = min(area_height, area_width) + + percent_change = (min_area_size - min_text_size) / delta_text_size + + font_size = round(font_size_a + (font_size_b - font_size_a) * percent_change) * 1 + + return (font_size, text, ) + FONTS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts") class DrawText: @classmethod @@ -10,7 +59,7 @@ def INPUT_TYPES(s): return { "required": { "text": ("STRING", { "multiline": True, "dynamicPrompts": True, "default": "Hello, World!" }), - "font": (sorted([f for f in os.listdir(FONTS_DIR) if f.endswith('.ttf') or f.endswith('.otf')]), ), + "font": (get_fonts(), ), "size": ("INT", { "default": 56, "min": 1, "max": 9999, "step": 1 }), "color": ("STRING", { "multiline": False, "default": "#FFFFFF" }), "background_color": ("STRING", { "multiline": False, "default": "#00000000" }), @@ -35,13 +84,7 @@ def execute(self, text, font, size, color, background_color, shadow_distance, sh from PIL import Image, ImageDraw, ImageFont, ImageColor, ImageFilter font = ImageFont.truetype(os.path.join(FONTS_DIR, font), size) - - lines = text.split("\n") - - # Calculate the width and height of the text - text_width = max(font.getbbox(line)[2] for line in lines) - line_height = font.getmask(text).getbbox()[3] + font.getmetrics()[1] # add descent to height - text_height = line_height * len(lines) + lines, text_width, text_height, line_height = calculate_metrics(text, font) if img_composite is not None: img_composite = T.ToPILImage()(img_composite.permute([0,3,1,2])[0]).convert('RGBA') @@ -103,8 +146,10 @@ def execute(self, text, font, size, color, background_color, shadow_distance, sh TEXT_CLASS_MAPPINGS = { "DrawText+": DrawText, + "DetectFontSize+": DetectFontSize, } TEXT_NAME_MAPPINGS = { "DrawText+": "🔧 Draw Text", + "DetectFontSize+": "🔧 Detect Font Size", } \ No newline at end of file From dd138bdf41d3aac0ad0c4e6f1a43459ca7fd27ad Mon Sep 17 00:00:00 2001 From: nomcycle Date: Mon, 29 Jul 2024 13:36:56 -0600 Subject: [PATCH 2/4] Scale with aspect if single line. --- text.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/text.py b/text.py index db3977a..038e6c1 100644 --- a/text.py +++ b/text.py @@ -3,13 +3,12 @@ from nodes import MAX_RESOLUTION import torchvision.transforms.v2 as T -def calculate_metrics (text, font): - lines = text.split("\n") +def calculate_metrics (lines, font): # Calculate the width and height of the text text_width = max(font.getbbox(line)[2] for line in lines) - line_height = font.getmask(text).getbbox()[3] + font.getmetrics()[1] # add descent to height + line_height = font.getmask(lines[0]).getbbox()[3] + font.getmetrics()[1] # add descent to height text_height = line_height * len(lines) - return lines, text_width, text_height, line_height + return text_width, text_height, line_height def get_fonts (): return sorted([f for f in os.listdir(FONTS_DIR) if f.endswith('.ttf') or f.endswith('.otf')]) @@ -21,6 +20,7 @@ def INPUT_TYPES(s): "required": { "text": ("STRING", { "multiline": True, "dynamicPrompts": True, "default": "Hello, World!" }), "font": (get_fonts(), ), + "scale": ("FLOAT", { "default": 1.0, "min": 0.001, "max": 100.0, "step": 0.01 }), "area_width": ("INT", { "default": 0, "min": 0, "max": 4096, "step": 1 }), "area_height": ("INT", { "default": 0, "min": 0, "max": 4096, "step": 1 }), } @@ -33,24 +33,29 @@ def INPUT_TYPES(s): def get_text_size (self, text, font, size): from PIL import ImageFont font = ImageFont.truetype(os.path.join(FONTS_DIR, font), size) - lines, text_width, text_height, line_height = calculate_metrics(text, font) - return text_width, text_height + text_width, text_height, line_height = calculate_metrics(text, font) + return text_width, text_height, line_height - def execute(self, text, font, area_width, area_height): + def execute(self, text, font, scale, area_width, area_height): + lines = text.split("\n") font_size_a, font_size_b = 10, 20 - text_width_a, text_height_a = self.get_text_size(text, font, font_size_a) - text_width_b, text_height_b = self.get_text_size(text, font, font_size_b) + text_width_a, text_height_a, line_height_a = self.get_text_size(lines, font, font_size_a) + text_width_b, text_height_b, line_height_b = self.get_text_size(lines, font, font_size_b) delta_text_size = max(text_width_b - text_width_a, text_height_b - text_height_a) - min_text_size = min(text_height_a, text_width_a) + single_line = len(lines) <= 1 + if not single_line: + text_height_a = line_height_a * (len(lines) + 1) + + min_text_size = max(text_height_a, text_width_a) min_area_size = min(area_height, area_width) percent_change = (min_area_size - min_text_size) / delta_text_size - font_size = round(font_size_a + (font_size_b - font_size_a) * percent_change) * 1 + font_size = round((font_size_a + (font_size_b - font_size_a) * percent_change) * scale * (area_width / area_height if single_line else 1.0)) - return (font_size, text, ) + return (2 if font_size <= 0 else font_size, text, ) FONTS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts") class DrawText: @@ -84,7 +89,8 @@ def execute(self, text, font, size, color, background_color, shadow_distance, sh from PIL import Image, ImageDraw, ImageFont, ImageColor, ImageFilter font = ImageFont.truetype(os.path.join(FONTS_DIR, font), size) - lines, text_width, text_height, line_height = calculate_metrics(text, font) + lines = text.split("\n") + text_width, text_height, line_height = calculate_metrics(lines, font) if img_composite is not None: img_composite = T.ToPILImage()(img_composite.permute([0,3,1,2])[0]).convert('RGBA') From 5590115027880b30f11468441d77bb3735aa2dfb Mon Sep 17 00:00:00 2001 From: nomcycle Date: Mon, 29 Jul 2024 15:19:59 -0600 Subject: [PATCH 3/4] Fixed edge cases. --- text.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/text.py b/text.py index 038e6c1..fc286ab 100644 --- a/text.py +++ b/text.py @@ -39,22 +39,19 @@ def get_text_size (self, text, font, size): def execute(self, text, font, scale, area_width, area_height): lines = text.split("\n") font_size_a, font_size_b = 10, 20 - text_width_a, text_height_a, line_height_a = self.get_text_size(lines, font, font_size_a) - text_width_b, text_height_b, line_height_b = self.get_text_size(lines, font, font_size_b) + text_width_a, text_height_a, _ = self.get_text_size(lines, font, font_size_a) + text_width_b, text_height_b, _ = self.get_text_size(lines, font, font_size_b) - delta_text_size = max(text_width_b - text_width_a, text_height_b - text_height_a) + text_aspect = text_width_a / text_height_a + area_aspect = area_width / area_height - single_line = len(lines) <= 1 - if not single_line: - text_height_a = line_height_a * (len(lines) + 1) - - min_text_size = max(text_height_a, text_width_a) - min_area_size = min(area_height, area_width) - - percent_change = (min_area_size - min_text_size) / delta_text_size - - font_size = round((font_size_a + (font_size_b - font_size_a) * percent_change) * scale * (area_width / area_height if single_line else 1.0)) + percent_change = 1.0 + # touching = min(area_height - text_height_a, area_width - text_width_a) + if area_aspect > text_aspect: + percent_change = (area_height - text_height_a) / (text_height_b - text_height_a) + else: percent_change = (area_width - text_width_a) / (text_width_b - text_width_a) + font_size = round((font_size_a + (font_size_b - font_size_a) * percent_change) * scale) return (2 if font_size <= 0 else font_size, text, ) FONTS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts") From a3fea9d51e2ccaf2362a1a72e9a61f4c049c4487 Mon Sep 17 00:00:00 2001 From: Sean Connor Date: Sat, 1 Mar 2025 10:08:36 -0700 Subject: [PATCH 4/4] Fixed overlap issue when row/col is set to 1. --- image.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/image.py b/image.py index 1891e23..34e37ca 100644 --- a/image.py +++ b/image.py @@ -600,6 +600,12 @@ def INPUT_TYPES(s): def execute(self, tiles, overlap_x, overlap_y, rows, cols): tile_h, tile_w = tiles.shape[1:3] + + if rows == 1: + overlap_y = 0 + if cols == 1: + overlap_x = 0 + tile_h -= overlap_y tile_w -= overlap_x out_w = cols * tile_w @@ -632,15 +638,8 @@ def execute(self, tiles, overlap_x, overlap_y, rows, cols): # feather the overlap on top if i > 0: mask[:, :overlap_y, :] *= torch.linspace(0, 1, overlap_y, device=tiles.device, dtype=tiles.dtype).unsqueeze(1) - # feather the overlap on bottom - #if i < rows - 1: - # mask[:, -overlap_y:, :] *= torch.linspace(1, 0, overlap_y, device=tiles.device, dtype=tiles.dtype).unsqueeze(1) - # feather the overlap on left if j > 0: mask[:, :, :overlap_x] *= torch.linspace(0, 1, overlap_x, device=tiles.device, dtype=tiles.dtype).unsqueeze(0) - # feather the overlap on right - #if j < cols - 1: - # mask[:, :, -overlap_x:] *= torch.linspace(1, 0, overlap_x, device=tiles.device, dtype=tiles.dtype).unsqueeze(0) mask = mask.unsqueeze(-1).repeat(1, 1, 1, tiles.shape[3]) tile = tiles[i * cols + j] * mask