From 0f93feb3d93d16dc898782a0438ab0310b15332a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fr=C3=A9d=C3=A9ric?= Date: Sun, 12 May 2024 22:54:34 +0200 Subject: [PATCH] add flowers add orig width/height to the grid node debug --- grid_node.py | 38 ++++++++++++----- map_node.py | 118 +++++++++++++++++++++++++++++++-------------------- path.py | 14 ++++++ point.py | 13 +++--- utils.py | 6 +-- 5 files changed, 122 insertions(+), 67 deletions(-) create mode 100644 path.py diff --git a/grid_node.py b/grid_node.py index edd9254..d9b3fd6 100644 --- a/grid_node.py +++ b/grid_node.py @@ -55,16 +55,26 @@ class BattlemapGrid: @classmethod def INPUT_TYPES(cls): - return {"required": { - "image": ("IMAGE",), - "grid_type": (cls._grid_type, {"default": "square"}), - "grid_side": ("INT", {"default": 64, "min": 10, "max": 2048}), - "line_width": ("INT", {"default": 1, "min": 1, "max": 20}), - "red": ("INT", {"default": 255, "min": 0, "max": 255}), - "green": ("INT", {"default": 255, "min": 0, "max": 255}), - "blue": ("INT", {"default": 255, "min": 0, "max": 255}), - "alpha": ("INT", {"default": 255, "min": 0, "max": 255}), - } + return { + "required": { + "image": ("IMAGE",), + "grid_type": (cls._grid_type, {"default": "square"}), + "grid_side": ("INT", {"default": 64, "min": 10, "max": 2048}), + "line_width": ("INT", {"default": 1, "min": 1, "max": 20}), + "red": ("INT", {"default": 255, "min": 0, "max": 255}), + "green": ("INT", {"default": 255, "min": 0, "max": 255}), + "blue": ("INT", {"default": 255, "min": 0, "max": 255}), + "alpha": ("INT", {"default": 255, "min": 0, "max": 255}) + }, + "optional": { + "orig_grid_width": ("INT", + {"default": None, "min": 0, "max": 128, + "forceInput": True}), + "orig_grid_height": ("INT", + {"default": None, "min": 00, "max": 128, + "forceInput": True}), + + } } RETURN_TYPES = ("IMAGE",) @@ -93,13 +103,19 @@ class BattlemapGrid: def grid_overlay(self, image: torch.Tensor, grid_type: str, grid_side: int, line_width: int, - red: int, green: int, blue: int, alpha: int): + red: int, green: int, blue: int, alpha: int, + orig_grid_width=None, orig_grid_height=None): image_pil = tensor_to_pil(image) # Create Grid draw = Draw(image_pil) pen = Pen((red, green, blue, alpha), line_width) center = Point(int(image_pil.width / 2), int(image_pil.height / 2)) + if (orig_grid_width and orig_grid_height): + width_rate = (image_pil.width / orig_grid_width) + height_rate = (image_pil.height / orig_grid_height) + if 0.99 <= (width_rate / height_rate) <= 1.01: + grid_side = round(image_pil.width / orig_grid_width) if grid_type == "square": self.square_grid(image_pil, draw, center, pen, grid_side) diff --git a/map_node.py b/map_node.py index 5b06b9b..01bd31d 100644 --- a/map_node.py +++ b/map_node.py @@ -1,10 +1,11 @@ from PIL import Image, ImageDraw, ImageColor, ImageFilter import math from dataclasses import dataclass +from colorsys import rgb_to_hsv, hsv_to_rgb from random import random, randint, seed, choice from .utils import pil_to_tensor, generate_noise from .point import Point -from colorsys import rgb_to_hsv, hsv_to_rgb +from .path import Path @dataclass @@ -14,7 +15,7 @@ class BattlemapMapGenerator: return {"required": { "node_seed": ( "INT", {"default": 0, "min": -1, "max": 0xffffffffffffffff, - "step": 1, "label": "seed"}), + "step": 1, "label": "seed", "forceInput": True}), "grid_width": ("INT", {"default": 24, "min": 10, "max": 128}), "grid_height": ("INT", {"default": 32, "min": 10, "max": 128}), "grid_side": ("INT", {"default": 32, "min": 10, "max": 2048}), @@ -83,12 +84,13 @@ class BattlemapMapGeneratorOutdoors(BattlemapMapGenerator): "road": ( "BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}), - "trees": ( - "BOOLEAN", - {"default": True, "label_off": "OFF", "label_on": "ON"}), "rocks": ( "BOOLEAN", {"default": True, "label_off": "OFF", "label_on": "ON"}), + "trees": ( + "BOOLEAN", + {"default": True, "label_off": "OFF", "label_on": "ON"}), + "positive": ("STRING", {"default": '', "multiline": True}), "negative": ("STRING", {"default": '', "multiline": True}), }) @@ -109,7 +111,12 @@ class BattlemapMapGeneratorOutdoors(BattlemapMapGenerator): return tuple(return_names) def generator_flowers(self, image, draw): - pass + for i in range(100, 500): + point = Point(randint(0, image.width), randint(0, image.height)) + size = randint(2, 6) + draw.ellipse([(point - size).coord(), (point + size).coord()], + fill=choice(list(ImageColor.colormap.keys())), + outline="black", width=2) def start_point(self, image): match randint(0, 3): @@ -124,38 +131,54 @@ class BattlemapMapGeneratorOutdoors(BattlemapMapGenerator): angle = point.angle_between(Point(image.width / 2, image.height / 2)) return point, angle + randint(-45, 45) - def generate_path(self, image, draw, color, - start_point, start_angle, width=20, - depth=0, max_depth=3): - if depth > max_depth: + def generate_path(self, image, paths: list[Path], start_point, start_angle, + width=20, + depth=0, depth_max=5): + if depth > depth_max or width <= 5: return point1, angle, length = start_point, start_angle, randint(100, 150) - point_list = [point1.coord()] + path = Path(width=width) + path.add_point(point1) while -10 <= point1.x <= image.width + 10 and -10 <= point1.y <= image.height + 10: point2 = point1.add_polar(length, angle) - point_list.append(point2.coord()) - if randint(0, 10) == 0: - self.generate_path(image, draw, color, - point1, angle + randint(-30, 30), - max(5, width + randint(-5, 5)), - depth + 1, max_depth) + path.add_point(point2) + match randint(0, 20): + case 0: + break + case 1 | 2: + new_angle = angle + randint(10, 30) * choice([-1, 1]) + self.generate_path(image, paths, point1, new_angle, + width=math.ceil(width * 2 / 3), + depth=depth + 1, depth_max=depth_max) + case _: + pass point1, angle, length = (point2, angle + randint(-20, 20), randint(20, 200)) - draw.line(point_list, fill="black", width=width + 2, joint="curve") - for i in range(width, 0, -1): + paths.append(path) + + def draw_path(self, draw, color, paths: list): + width_max = 0 + for path in paths: + width_max = max(width_max, path.width) + draw.line(path.coord(), fill="black", width=path.width + 2, + joint="curve") + for i in range(width_max, 0, -1): h, s, v = rgb_to_hsv(*ImageColor.getcolor(color, "RGB")) r, g, b = map(int, hsv_to_rgb(h, s, v / i ** 0.3)) - draw.line(point_list, fill=(r, g, b), width=i, joint="curve") + for path in filter(lambda x: x.width <= width_max, paths): + draw.line(path.coord(), fill=(r, g, b), width=i, joint="curve") def generate_rivers(self, image, draw): point, angle = self.start_point(image) - color = "blue" - self.generate_path(image, draw, color, point, angle) + paths = list() + self.generate_path(image, paths, point, angle) + self.draw_path(draw, "blue", paths) def generate_roads(self, image, draw): point, angle = self.start_point(image) - color = "saddlebrown" - self.generate_path(image, draw, color, point, angle) + paths = list() + self.generate_path(image, paths, point, angle) + self.draw_path(draw, "saddlebrown", paths) def generate_stars(self, image, draw, size_color): center = Point(randint(0, image.width), randint(0, image.height)) @@ -164,8 +187,11 @@ class BattlemapMapGeneratorOutdoors(BattlemapMapGenerator): for angle in range(0, 360, 5): length = max(size / 5, size * random() * size_multiplicator) point = center.add_polar(length, angle) + witdh = randint(5, 10) draw.line([center.coord(), point.coord()], - fill=color, width=7 + randint(-2, 2)) + fill="black", width=witdh + 2) + draw.line([center.coord(), point.coord()], + fill=color, width=witdh) generate_noise(image, center - size, center + size, 10) def generate_polygon(self, image, draw, size_color, nb_point): @@ -183,23 +209,23 @@ class BattlemapMapGeneratorOutdoors(BattlemapMapGenerator): def generate_ellipses(self, image, draw, size_color): center = Point(randint(0, image.width), randint(0, image.height)) size_multiplicator = random() - max_size = 0 + size_max = 0 for size, color in size_color: - max_size = max(max_size, math.ceil(size * size_multiplicator)) + size_max = max(size_max, math.ceil(size * size_multiplicator)) point1 = center - math.ceil(size * size_multiplicator) - randint(0, 10) point2 = center + math.ceil(size * size_multiplicator) + randint(0, 10) draw.ellipse([point1.coord(), point2.coord()], fill=color, outline="black", width=2) - generate_noise(image, center - max_size, center + max_size, 10) + generate_noise(image, center - size_max, center + size_max, 10) def generate_rocks(self, image, draw): for i in range(10): - self.generate_ellipses(image, draw, [(60, "#111111"), - (50, "darkgray"), - (40, "gray")]) - # choice([3, 4, 5, 6, 8, 9, 10]) + self.generate_polygon(image, draw, [(50, "#111111"), + (35, "darkgray"), + (25, "gray")], + choice([3, 4, 5, 6, 8, 9])) def generate_trees(self, image, draw): for i in range(100): @@ -209,37 +235,37 @@ class BattlemapMapGeneratorOutdoors(BattlemapMapGenerator): (20, "lightgreen")]) def map_generator(self, node_seed, grid_width, grid_height, - grid_side, bg_color, river, road, trees, rocks, + grid_side, bg_color, river, road, rocks, trees, positive, negative): seed(node_seed) - image_pil, width, height, grid_width, grid_height, grid_side = ( + image, width, height, grid_width, grid_height, grid_side = ( self._map_generator(node_seed, grid_width, grid_height, grid_side, bg_color)) - draw = ImageDraw.Draw(image_pil) - + draw = ImageDraw.Draw(image) + self.generator_flowers(image, draw) if river: - self.generate_rivers(image_pil, draw) + self.generate_rivers(image, draw) positive += "blue river, water." else: negative += "blue river, water." if road: - self.generate_roads(image_pil, draw) + self.generate_roads(image, draw) positive += "saddlebrown road." else: negative += "road." - if trees: - self.generate_trees(image_pil, draw) - positive += "gray rocks." - else: - negative += "rocks." - if rocks: - self.generate_rocks(image_pil, draw) + self.generate_rocks(image, draw) positive += "green trees." else: negative += "trees." - return (pil_to_tensor(image_pil), + if trees: + self.generate_trees(image, draw) + positive += "gray rocks." + else: + negative += "rocks." + + return (pil_to_tensor(image), width, height, grid_width, grid_height, grid_side, positive, negative) diff --git a/path.py b/path.py new file mode 100644 index 0000000..b054a80 --- /dev/null +++ b/path.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass, field +from .point import Point + + +@dataclass +class Path: + width: int + points: list[Point] = field(default_factory=list) + + def add_point(self, point: Point): + self.points.append(point) + + def coord(self): + return [point.coord() for point in self.points] diff --git a/point.py b/point.py index 45052b2..6f79832 100644 --- a/point.py +++ b/point.py @@ -1,27 +1,26 @@ from dataclasses import dataclass import math - +from typing import Union @dataclass class Point: x: int y: int - def __add__(self, value): + def __add__(self, value: Union[int, float, "Point"]) -> "Point": if isinstance(value, (int, float)): return Point(self.x + value, self.y + value) elif isinstance(value, (tuple, list)): return Point(self.x + value[0], self.y + value[1]) return Point(self.x + value.x, self.y + value.y) - def __sub__(self, value): + def __sub__(self, value: Union[int, float, "Point"]) -> "Point": if isinstance(value, (int, float)): return Point(self.x - value, self.y - value) elif isinstance(value, (tuple, list)): return Point(self.x - value[0], self.y - value[1]) return Point(self.x - value.x, self.y - value.y) - def __iter__(self): yield self.x yield self.y @@ -29,10 +28,10 @@ class Point: def coord(self): return (self.x, self.y) - def add_polar(self, length, angle): - return self + ( + def add_polar(self, length: float, angle: float) -> "Point": + return self + Point( math.ceil(length * math.cos(angle * math.pi / 180)), -math.ceil(length * math.sin(angle * math.pi / 180))) - def angle_between(self, target): + def angle_between(self, target: "Point") -> float: return math.degrees(math.atan2(self.y - target.y, target.x - self.x)) diff --git a/utils.py b/utils.py index e0287c6..294a979 100644 --- a/utils.py +++ b/utils.py @@ -5,20 +5,20 @@ from random import randint from .point import Point -def tensor_to_pil(image_tensor: torch.tensor): +def tensor_to_pil(image_tensor: torch.tensor) -> Image: image_np = image_tensor.cpu().numpy() image_pil = Image.fromarray((image_np.squeeze(0) * 255).astype(np.uint8)) return image_pil.convert("RGBA") -def pil_to_tensor(image_pil: Image): +def pil_to_tensor(image_pil: Image) -> torch.Tensor: image_tensor_out = torch.tensor( np.array(image_pil).astype(np.float32) / 255.0) return torch.unsqueeze(image_tensor_out, 0) def generate_noise(image, point1: Point, point2: Point, padding=0): - def noise_color(i, j, color_index): + def noise_color(i: int, j: int, color_index: int) -> int: noise = randint(-64, 64) return max(0, min(pixels[i, j][color_index] + noise, 255))