Skip to content

📱 CR Simple Meme Template

Documentation

  • Class name: CR Simple Meme Template
  • Category: 🧩 Comfyroll Studio/👾 Graphics/📱 Template
  • Output node: False

This node is designed to facilitate the creation of simple meme templates, allowing users to easily generate memes by providing customizable text and image inputs. It abstracts the complexity of meme generation into a user-friendly interface, enabling quick and creative meme creation.

Input types

Required

  • image
    • The 'image' input allows users to provide the base image on which the meme text will be overlaid. This is a crucial component for meme creation, setting the visual context for the text.
    • Comfy dtype: IMAGE
    • Python dtype: Image
  • preset
    • The 'preset' input offers predefined meme formats that users can select to quickly generate popular meme styles, enhancing the ease of meme creation.
    • Comfy dtype: COMBO[STRING]
    • Python dtype: str
  • text_top
    • The 'text_top' input allows users to specify the text that appears at the top of the meme, enabling personalized messages or jokes.
    • Comfy dtype: STRING
    • Python dtype: str
  • text_bottom
    • The 'text_bottom' input allows users to specify the text that appears at the bottom of the meme, complementing the top text for a complete meme experience.
    • Comfy dtype: STRING
    • Python dtype: str
  • font_name
    • The 'font_name' input lets users choose the font style for the meme text, allowing for further customization of the meme's appearance.
    • Comfy dtype: COMBO[STRING]
    • Python dtype: str
  • max_font_size
    • The 'max_font_size' input determines the maximum size of the meme text, ensuring the text remains readable and visually appealing.
    • Comfy dtype: INT
    • Python dtype: int
  • font_color
    • The 'font_color' input allows users to select the color of the meme text, enabling visual contrast and emphasis.
    • Comfy dtype: COMBO[STRING]
    • Python dtype: str
  • font_outline
    • The 'font_outline' input provides options for adding an outline to the meme text, enhancing readability against varied backgrounds.
    • Comfy dtype: COMBO[STRING]
    • Python dtype: str
  • bar_color
    • The 'bar_color' input enables users to choose the color of optional bars that can be added to the top and/or bottom of the meme for aesthetic or thematic purposes.
    • Comfy dtype: COMBO[STRING]
    • Python dtype: str
  • bar_options
    • The 'bar_options' input allows users to select the placement of optional bars in the meme, offering additional layout customization.
    • Comfy dtype: COMBO[STRING]
    • Python dtype: str

Optional

  • font_color_hex
    • The 'font_color_hex' input offers an alternative way to specify the meme text color using hexadecimal color codes, providing precise color customization.
    • Comfy dtype: STRING
    • Python dtype: str
  • bar_color_hex
    • The 'bar_color_hex' input offers an alternative way to specify the color of optional bars using hexadecimal color codes, allowing for precise color matching.
    • Comfy dtype: STRING
    • Python dtype: str

Output types

  • image
    • Comfy dtype: IMAGE
    • The 'image' output represents the final meme generated by the node, ready for sharing or further use.
    • Python dtype: Image
  • show_help
    • Comfy dtype: STRING
    • The 'show_help' output provides guidance or additional information about the meme generation process, assisting users in creating their desired memes.
    • Python dtype: str

Usage tips

Source code

class CR_SimpleMemeTemplate:

    @classmethod
    def INPUT_TYPES(s):

        font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts")       
        file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")]
        bar_opts = ["no bars", "top", "bottom", "top and bottom"]
        simple_meme_presets = ["custom",
                               "One Does Not Simply ... MEME IN COMFY",
                               "This is fine.",
                               "Good Morning ... No Such Thing!"]        

        return {"required": {
                    "image": ("IMAGE",),
                    "preset": (simple_meme_presets,),   
                    "text_top": ("STRING", {"multiline": True, "default": "text_top"}),
                    "text_bottom": ("STRING", {"multiline": True, "default": "text_bottom"}),
                    "font_name": (file_list,),
                    "max_font_size": ("INT", {"default": 150, "min": 20, "max": 2048}),
                    "font_color": (COLORS,),
                    "font_outline": (["none", "thin", "thick", "extra thick"],),
                    "bar_color": (COLORS,),
                    "bar_options": (bar_opts,),
                },
                "optional": {
                    "font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}),
                    "bar_color_hex": ("STRING", {"multiline": False, "default": "#000000"})
                }         
    }

    RETURN_TYPES = ("IMAGE", "STRING", )
    RETURN_NAMES = ("image", "show_help", )  
    FUNCTION = "make_meme"
    CATEGORY = icons.get("Comfyroll/Graphics/Template")

    def make_meme(self, image, preset,
                  text_top, text_bottom,
                  font_name, max_font_size, font_color, font_outline,
                  bar_color, bar_options,
                  font_color_hex='#000000', bar_color_hex='#000000'):

        # Get RGB values for the text and bar colors
        text_color = get_color_values(font_color, font_color_hex, color_mapping)
        bar_color = get_color_values(bar_color, bar_color_hex, color_mapping) 

        total_images = []

        for img in image:

            # Calculate the height factor
            if bar_options == "top":
                height_factor = 1.2
            elif bar_options == "bottom":
                height_factor = 1.2
            elif bar_options == "top and bottom":
                height_factor = 1.4
            else:
                height_factor = 1.0

            if preset == "One Does Not Simply ... MEME IN COMFY":
                text_top = "One Does Not Simply"
                text_bottom = "MEME IN COMFY"
            if preset == "This is fine.":
                text_top = "This is fine."
                text_bottom = ""            
            if preset == "Good Morning ... No Such Thing!":
                text_top = "Good Morning"
                text_bottom = "\"No Such Thing!\""  

            # Create PIL images for the image and text bars
            back_image = tensor2pil(img)   
            size = back_image.width, int(back_image.height * height_factor)
            result_image = Image.new("RGB", size)

            # Define font settings
            #font_file = "fonts\\" + str(font_name)
            font_file = os.path.join("fonts", font_name)
            resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file)

            # Create the drawing context
            draw = ImageDraw.Draw(result_image)

            # Create two color bars at the top and bottom
            bar_width = back_image.width
            bar_height = back_image.height // 5    ### add parameter for this in adv node
            top_bar = Image.new("RGB", (bar_width, bar_height), bar_color)
            bottom_bar = Image.new("RGB", (bar_width, bar_height), bar_color)

            # Composite the result image onto the input image
            if bar_options == "top" or bar_options == "top and bottom":
                image_out = result_image.paste(back_image, (0, bar_height))
            else:
                image_out = result_image.paste(back_image, (0, 0))

            # Get the font size and draw the text
            if bar_options == "top" or bar_options == "top and bottom":
                result_image.paste(top_bar, (0, 0))
                font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
                draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, font_outline)

            if bar_options == "bottom" or bar_options == "top and bottom":
                result_image.paste(bottom_bar, (0, (result_image.height - bar_height)))
                font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
                if bar_options == "bottom":
                    y_position = back_image.height
                else:
                    y_position = bar_height + back_image.height
                draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, font_outline)

            # Overlay text on image
            if bar_options == "bottom" and text_top > "":
                font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
                draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, font_outline)

            if (bar_options == "top" or bar_options == "none") and text_bottom > "":
                font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
                y_position = back_image.height
                draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, font_outline)

            if bar_options == "no bars" and text_bottom > "":
                font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size)
                y_position = back_image.height - bar_height
                draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, font_outline)

            if bar_options == "no bars" and text_top > "":
                font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size)
                draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, font_outline)

            #image_out = np.array(result_image).astype(np.float32) / 255.0
            #image_out = torch.from_numpy(image_out).unsqueeze(0)          

            # Convert the PIL image back to a torch tensor
            #return (pil2tensor(image_out), show_help, )
            #return (image_out, show_help, )

            # Convert to tensor
            out_image = np.array(result_image.convert("RGB")).astype(np.float32) / 255.0
            out_image = torch.from_numpy(out_image).unsqueeze(0)
            total_images.append(out_image)

        # Batch the images
        images_out = torch.cat(total_images, 0)

        show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Template-Nodes#cr-simple-meme-template"

        return (images_out, show_help, )