Skip to content

FL Hexagonal Pattern

Documentation

  • Class name: FL_HexagonalPattern
  • Category: 🏵️Fill Nodes
  • Output node: False

The FL_HexagonalPattern node is designed to transform a collection of images into a hexagonal pattern, adjusting the size of hexagons based on the image dimensions and applying optional shadow effects for enhanced visual depth. It allows for customization of the hexagonal pattern through parameters such as hexagon size, shadow properties, and background color, enabling a versatile approach to image styling.

Input types

Required

  • images
    • A collection of images to be transformed into a hexagonal pattern. This parameter is essential for the node's operation, serving as the primary input around which the hexagonal pattern is generated.
    • Comfy dtype: IMAGE
    • Python dtype: List[torch.Tensor]

Optional

  • hexagon_size
    • Specifies the desired size of the hexagons in the pattern, affecting the overall appearance and granularity of the hexagonal tiling.
    • Comfy dtype: INT
    • Python dtype: int
  • shadow_offset
    • Determines the offset distance for the shadow effect applied to each hexagon, enhancing the pattern's visual depth.
    • Comfy dtype: INT
    • Python dtype: int
  • shadow_color
    • Defines the color of the shadow effect, allowing for customization of the shadow's appearance.
    • Comfy dtype: STRING
    • Python dtype: str
  • background_color
    • Sets the background color of the output image, providing a base for the hexagonal pattern.
    • Comfy dtype: STRING
    • Python dtype: str
  • rotation
    • Controls the rotation angle of each hexagon in the pattern, offering an additional dimension of stylistic customization.
    • Comfy dtype: FLOAT
    • Python dtype: float
  • spacing
    • Adjusts the spacing between individual hexagons in the pattern, influencing the pattern's density and overall aesthetic.
    • Comfy dtype: FLOAT
    • Python dtype: float

Output types

  • image
    • Comfy dtype: IMAGE
    • The transformed image with the applied hexagonal pattern, incorporating any specified shadow effects and background color.
    • Python dtype: torch.Tensor

Usage tips

  • Infra type: CPU
  • Common nodes: unknown

Source code

class FL_HexagonalPattern:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "images": ("IMAGE",),
            },
            "optional": {
                "hexagon_size": ("INT", {"default": 100, "min": 50, "max": 500, "step": 10}),
                "shadow_offset": ("INT", {"default": 5, "min": 0, "max": 20, "step": 1}),
                "shadow_color": ("STRING", {"default": "purple"}),
                "background_color": ("STRING", {"default": "black"}),
                "rotation": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 360.0, "step": 1.0}),
                "spacing": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.1}),
            },
        }

    RETURN_TYPES = ("IMAGE",)
    FUNCTION = "hexagonal_pattern"
    CATEGORY = "🏵️Fill Nodes"

    def t2p(self, t):
        if t is not None:
            i = 255.0 * t.cpu().numpy().squeeze()
            p = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
        return p

    def create_hexagon_mask(self, size):
        mask = Image.new("L", (size, size), 0)
        draw = ImageDraw.Draw(mask)
        draw.regular_polygon((size // 2, size // 2, size // 2), 6, fill=255)
        return mask

    def calculate_adjusted_hexagon_size(self, width, height, hexagon_size, spacing):
        horizontal_count = math.ceil(width / (hexagon_size * spacing))
        vertical_count = math.ceil(height / (hexagon_size * spacing * math.sqrt(3) / 2))

        adjusted_width = width / horizontal_count
        adjusted_height = height / (vertical_count * math.sqrt(3) / 2)

        return min(adjusted_width, adjusted_height) / spacing

    def hexagonal_pattern(self, images, hexagon_size=100, shadow_offset=5, shadow_color="black", shadow_opacity=0.5,
                          background_color="white", rotation=0.0, spacing=1.0):
        out = []
        total_images = len(images)
        for i, img_tensor in enumerate(images, start=1):
            p = self.t2p(img_tensor)
            width, height = p.size

            adjusted_hexagon_size = self.calculate_adjusted_hexagon_size(width, height, hexagon_size, spacing)
            hexagon_mask = self.create_hexagon_mask(int(adjusted_hexagon_size))

            output_image = Image.new("RGBA", (width, height), background_color)

            for y in range(0, height, int(adjusted_hexagon_size * spacing * math.sqrt(3) / 2)):
                for x in range(0, width, int(adjusted_hexagon_size * spacing)):
                    if y % (2 * int(adjusted_hexagon_size * spacing * math.sqrt(3) / 2)) == int(adjusted_hexagon_size * spacing * math.sqrt(3) / 2):
                        x += int(adjusted_hexagon_size * spacing) // 2

                    cropped_hexagon = p.crop((x, y, x + int(adjusted_hexagon_size), y + int(adjusted_hexagon_size))).rotate(rotation, expand=True)

                    shadow = Image.new("RGBA", cropped_hexagon.size, (0, 0, 0, 0))
                    shadow_mask = hexagon_mask.copy().resize(cropped_hexagon.size)
                    shadow.paste(shadow_color, (shadow_offset, shadow_offset), shadow_mask)
                    shadow.putalpha(int(255 * shadow_opacity))

                    output_image.paste(shadow, (x + shadow_offset, y + shadow_offset), shadow_mask)
                    output_image.paste(cropped_hexagon, (x, y), shadow_mask)

            o = np.array(output_image.convert("RGB")).astype(np.float32) / 255.0
            o = torch.from_numpy(o).unsqueeze(0)
            out.append(o)

            # Print progress update
            progress = i / total_images * 100
            sys.stdout.write(f"\rProcessing images: {progress:.2f}%")
            sys.stdout.flush()

        # Print a new line after the progress update
        print()

        out = torch.cat(out, 0)
        return (out,)