diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 81a17f8..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Raykosan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index eeea0d0..0000000 --- a/README.md +++ /dev/null @@ -1,96 +0,0 @@ -## 🦊 ComfyUI_RaykoStudio -ComfyUI_RaykoStudio — это набор кастомных нод для ComfyUI, предоставляющий дополнительные возможности для работы с изображениями - -![Demo](web/preview.png) - -## 🦊 RS_RusTextOverlay Node - -## 🔥 Описание - -`RS_RusTextOverlay` позволяет накладывать текст на изображение в области, заданной маской. Нода поддерживает поворот текста в соответствии с углом наклона маски, автоматический подбор размера шрифта, кастомные шрифты, перенос строк и настройку прозрачности текста. - -### Возможности - -- Наложение текста на изображение в области, заданной маской. -- Автоматический поворот текста в соответствии с углом наклона маски (опционально). -- Поддержка кастомных шрифтов (`.ttf` или `.otf`), загружаемых из папки `fonts`. -- Автоматический подбор размера шрифта для вписывания текста в область маски. -- Настройка цвета текста и прозрачности (от 0 до 100%). -- Настройка выравнивания текста (вертикальное: top/center/bottom, горизонтальное: left/center/right). -- Настройка отступов (padding) вокруг текста. - -### Входы - -- `image`: Изображение (тензор формата RGB). -- `mask`: Маска (тензор формата L), определяющая область для текста. - -### Выходы - -- `IMAGE`: Изображение с наложенным текстом (тензор формата RGB). - -## ⚙️ Параметры - -- `text`: Текст для наложения. Поддерживает перенос строк. -- `font_name`: Выберите шрифт из списка доступных в папке `fonts` (или используйте `default`). -- `text_color`: Цвет текста (в формате HEX, например `#FFFFFF`). -- `text_opacity`: Прозрачность текста (0–100%, где 0 — полностью прозрачный, 100 — полностью непрозрачный). -- `min_font_size`: Минимальный размер шрифта (если текст не помещается, используется шрифт по умолчанию). -- `padding`: Отступы вокруг текста внутри маски. -- `vertical_align`: Вертикальное выравнивание текста (top/center/bottom). -- `horizontal_align`: Горизонтальное выравнивание текста (left/center/right). -- `rotate_with_mask`: Включить/выключить поворот текста в соответствии с углом наклона маски. - -## 🛠 Установка - -1. Клонируйте репозиторий в папку `ComfyUI/custom_nodes/`: - -git clone [https://github.com/Raykosan/ComfyUI_RaykoStudio.git](https://github.com/Raykosan/ComfyUI_RaykoStudio) - -Перезагрузите ComfyU - -или - -скопируйте папку RaykoStudio_Nodes в папку: ComfyUI/custom_nodes/ - -Перезагрузите ComfyU - -3. Убедитесь, что папка `fonts` содержит шрифты (`.ttf` или `.otf`). Вы можете добавить свои шрифты в эту папку. Если папка пуста или отсутствует, будет использоваться шрифт по умолчанию. - -4. Установите необходимые зависимости, указанные в `requirements.txt`: pip install -r requirements.txt - Перезапустите ComfyUI, чтобы нода стала доступна. - -## ⛓️‍💥 Зависимости - -Для работы ноды требуются следующие библиотеки (указаны в requirements.txt): - - torch>=1.7.0 - numpy>=1.19.0 - Pillow>=8.0.0 - opencv-python>=4.5.0 - -## 🎛 Использование - -После установки найдите ноду в ComfyUI под названием 🦊 RS_RusTextOverlay (категория: 🦊 RaykoStudio/Image). -Подключите входные данные (image и mask) и настройте параметры. -Пример рабочего процесса можно найти в папке examples: -- example workflow.json: Пример рабочего процесса для ComfyUI. -- example.png: Скриншот результата. - - -## 🤝 Сообщение об ошибках (Issues) - -Если вы столкнулись с проблемой или нашли баг: - -Проверьте раздел Issues на GitHub, возможно, проблема уже известна. -Если проблема новая, создайте новый Issue, описав: -- Версию ComfyUI и Python. -- Описание проблемы и шаги для ее воспроизведения. -- Скриншоты или логи ошибок (если есть). - -## 📜 Лицензия - -MIT License. Используйте ноду на свое усмотрение, но без каких-либо гарантий. - -## ❤️ Благодарности - -Спасибо сообществу ComfyUI за вдохновение и поддержку! Если вам понравилась эта нода, не забудьте поставить звездочку на GitHub! diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 6539a18..0000000 --- a/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .nodes.russian_text import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS - -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/examples/Exemple.json b/examples/Example.json similarity index 100% rename from examples/Exemple.json rename to examples/Example.json diff --git a/examples/example.png b/examples/example.png index 4f7d9bc..f69522f 100644 Binary files a/examples/example.png and b/examples/example.png differ diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 9512486..0000000 --- a/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "🦊 RS_RusTextOverlay", - "author": "RaykoStudio", - "version": "1.0.0", - "comfyui": "0.3.27", - "description": "A node for ComfyUI that allows you to overlay text on an image in an area defined by a mask, with support for text rotation, custom fonts, line breaks and transparency.", - "node_classes": ["RS_RusTextOverlay"], - "tags": ["image", "mask", "text", "overlay", "adjustment"], - "requirements": ["torch>=2.5.0"] -} \ No newline at end of file diff --git a/nodes/__init__.py b/nodes/__init__.py deleted file mode 100644 index dbea6c2..0000000 --- a/nodes/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from .russian_text import RS_RusTextOverlay - -NODE_CLASS_MAPPINGS = { - "RS_RusTextOverlay": RS_RusTextOverlay -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "RS_RusTextOverlay": "🦊 RS Русский Текст" -} - -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] \ No newline at end of file diff --git a/nodes/russian_text.py b/nodes/russian_text.py deleted file mode 100644 index 92764f9..0000000 --- a/nodes/russian_text.py +++ /dev/null @@ -1,244 +0,0 @@ -# nodes/russian_text.py -import os -import torch -import numpy as np -from PIL import Image, ImageDraw, ImageFont -import cv2 - -class RS_RusTextOverlay: - def __init__(self): - print("\033[93m🦊\033[0m \033[93mRaykoStudio - Russian Text Overlay Node \033[92mLOADED\033[0m") - - @classmethod - def INPUT_TYPES(cls): - current_dir = os.path.dirname(os.path.abspath(__file__)) - fonts_dir = os.path.join(os.path.dirname(current_dir), "fonts") - - font_list = [] - if os.path.exists(fonts_dir): - for f in os.listdir(fonts_dir): - if f.lower().endswith(('.ttf', '.otf')): - font_list.append(f) - - if not font_list: - font_list = ["default"] - print("RS_RusTextOverlay: No fonts found, using default") - - return { - "required": { - "image": ("IMAGE",), - "mask": ("MASK",), - "text": ("STRING", { - "default": "Текст\nс переносом\tи табуляцией", - "multiline": True - }), - "font_name": (font_list, {"default": font_list[0]}), - "text_color": ("COLOR", {"default": "#FFFFFF"}), - "text_opacity": ("INT", {"default": 100, "min": 0, "max": 100}), - "min_font_size": ("INT", {"default": 8, "min": 1, "max": 500}), - "padding": ("INT", {"default": 5, "min": 0, "max": 100}), - "vertical_align": (["top", "center", "bottom"], {"default": "center"}), - "horizontal_align": (["left", "center", "right"], {"default": "center"}), - "rotate_with_mask": ("BOOLEAN", {"default": True}), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "apply_text" - CATEGORY = "🦊 RaykoStudio/Image" - - def apply_text(self, image, mask, text, font_name, text_color, text_opacity, min_font_size, padding, vertical_align, horizontal_align, rotate_with_mask): - try: - # Конвертация tensor -> PIL - image_pil = Image.fromarray((image[0].cpu().numpy() * 255).astype(np.uint8), 'RGB') - mask_pil = Image.fromarray((mask[0].cpu().numpy() * 255).astype(np.uint8), 'L') - - # Получаем область маски - bbox = mask_pil.getbbox() - if not bbox: - return (image,) - - x1, y1, x2, y2 = bbox - mask_width = x2 - x1 - 2*padding - mask_height = y2 - y1 - 2*padding - - # Загрузка шрифта - if font_name != "default": - font_path = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - "fonts", - font_name - ) - - font = self.find_optimal_font(font_path, text, mask_width, mask_height, min_font_size) - else: - font = ImageFont.load_default() - - # Преобразуем прозрачность (0-100%) в альфа-канал (0-255) - alpha = int((text_opacity / 100.0) * 255) - if text_color.startswith('#'): - text_color = text_color[1:] - r = int(text_color[0:2], 16) - g = int(text_color[2:4], 16) - b = int(text_color[4:6], 16) - text_color_rgba = (r, g, b, alpha) - - # Создаем текстовый слой - text_layer = Image.new("RGBA", image_pil.size, (0, 0, 0, 0)) - draw = ImageDraw.Draw(text_layer) - - # Разбиваем текст на строки - lines = text.split('\n') - line_heights = [] - line_widths = [] - for line in lines: - line = line.replace('\t', ' ') - text_bbox = draw.textbbox((0, 0), line, font=font) - line_widths.append(text_bbox[2] - text_bbox[0]) - line_heights.append(text_bbox[3] - text_bbox[1]) - - total_text_height = sum(line_heights) - max_text_width = max(line_widths) - - if rotate_with_mask: - mask_np = np.array(mask_pil) - contours, _ = cv2.findContours(mask_np, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - if contours: - contour = max(contours, key=cv2.contourArea) - points = contour.reshape(-1, 2).astype(np.float32) - mean = np.mean(points, axis=0) - cov = np.cov(points.T) - eigenvalues, eigenvectors = np.linalg.eigh(cov) - main_axis = eigenvectors[:, np.argmax(eigenvalues)] - angle = np.arctan2(main_axis[1], main_axis[0]) * 180 / np.pi - - leftmost = points[np.argmin(points[:, 0])] - rightmost = points[np.argmax(points[:, 0])] - direction_vector = rightmost - leftmost - direction_angle = np.arctan2(direction_vector[1], direction_vector[0]) * 180 / np.pi - - angle_diff = (direction_angle - angle) % 360 - if angle_diff > 90 and angle_diff < 270: - angle += 180 - angle = angle % 360 - - if 90 < angle <= 270: - angle += 180 - angle = angle % 360 - - angle = -angle - - temp_size = int(max(max_text_width, total_text_height) * 3) - temp_text = Image.new("RGBA", (temp_size, temp_size), (0, 0, 0, 0)) - temp_draw = ImageDraw.Draw(temp_text) - - center_x, center_y = temp_size // 2, temp_size // 2 - - current_y = center_y - total_text_height // 2 - for line, line_height, line_width in zip(lines, line_heights, line_widths): - line = line.replace('\t', ' ') - pos_x = center_x - line_width // 2 - temp_draw.text( - (pos_x, current_y), - line, - font=font, - fill=text_color_rgba, - anchor="lt" - ) - current_y += line_height - - rotated_text = temp_text.rotate( - angle, - center=(center_x, center_y), - resample=Image.BICUBIC, - expand=False - ) - - rot_bbox = rotated_text.getbbox() - if rot_bbox: - rot_width = rot_bbox[2] - rot_bbox[0] - rot_height = rot_bbox[3] - rot_bbox[1] - - mask_center_x = (x1 + x2) / 2 - mask_center_y = (y1 + y2) / 2 - - paste_x = int(mask_center_x - rot_width / 2) - paste_y = int(mask_center_y - rot_height / 2) - - text_layer.paste( - rotated_text.crop(rot_bbox), - (paste_x, paste_y), - rotated_text.crop(rot_bbox) - ) - else: - print("Ошибка: не удалось определить bounding box повернутого текста") - else: - if vertical_align == "top": - current_y = y1 + padding - elif vertical_align == "bottom": - current_y = y2 - total_text_height - padding - else: - current_y = y1 + (mask_height - total_text_height) // 2 + padding - - for line, line_height, line_width in zip(lines, line_heights, line_widths): - line = line.replace('\t', ' ') - pos_x = { - "left": x1 + padding, - "right": x2 - line_width - padding, - "center": x1 + (mask_width - line_width) // 2 + padding - }[horizontal_align] - draw.text( - (pos_x, current_y), - line, - font=font, - fill=text_color_rgba, - anchor="lt" - ) - current_y += line_height - - result = Image.alpha_composite( - image_pil.convert("RGBA"), - text_layer - ).convert("RGB") - - return (torch.from_numpy(np.array(result).astype(np.float32) / 255.0).unsqueeze(0),) - - except Exception as e: - print(f"Ошибка в RS_RusTextOverlay: {str(e)}") - return (image,) - - def find_optimal_font(self, font_path, text, max_width, max_height, min_size): - temp_img = Image.new("RGB", (max_width, max_height)) - draw = ImageDraw.Draw(temp_img) - - lines = text.split('\n') - for size in range(500, min_size - 1, -1): - try: - font = ImageFont.truetype(font_path, size) - fits = True - total_height = 0 - max_line_width = 0 - for line in lines: - line = line.replace('\t', ' ') - bbox = draw.textbbox((0, 0), line, font=font) - line_width = bbox[2] - bbox[0] - line_height = bbox[3] - bbox[1] - total_height += line_height - max_line_width = max(max_line_width, line_width) - if line_width > max_width: - fits = False - break - if fits and total_height <= max_height: - return font - except Exception as e: - print(f"RS_RusTextOverlay: Failed to load font {font_path} with size {size}: {str(e)}") # Отладка: ошибка загрузки шрифта - continue - return ImageFont.load_default() - -NODE_CLASS_MAPPINGS = { - "RS_RusTextOverlay": RS_RusTextOverlay -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "RS_RusTextOverlay": "🦊 RS RusTextOverlay" -} \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index e94988b..0000000 --- a/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "comfyui-rs-text-overlay-node", - "version": "1.0.0", - "description": "A node for ComfyUI that allows you to overlay text on an image in an area defined by a mask, with support for text rotation, custom fonts, line breaks and transparency.", - "author": "Raykosan", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/Raykosan/ComfyUI_RaykoStudio" - }, - "comfyui": { - "nodes": [ - "nodes/russian_text.py" - ] - } -} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index d2446aa..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -torch>=2.0.0 -Pillow>=8.0.0 -numpy>=1.19.0 -opencv-python-headless>=4.2.0 \ No newline at end of file diff --git a/web/preview.png b/web/preview.png deleted file mode 100644 index 83cd8e8..0000000 Binary files a/web/preview.png and /dev/null differ