Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import sys
- import os
- import discord.ext
- # this block here walks up the directory structure until it finds the bot.py file
- # once it does, it sets the sys.path[0] to that directory so imports work correctly
- while True:
- if os.path.exists('bot.py'):
- sys.path[0] = os.getcwd()
- break
- sys.path[0] = "/".join(sys.path[0].split("/")[0:-1]) # basically go up one folder
- # bunch of image and data imports here
- import PIL.ImageColor
- import PIL.ImageFont
- import easy_pil # library for easier image editing (like a wrapper)
- import PIL
- from io import BytesIO # used later to store the final image in memory (instead of saving)
- from constants import fruit_values # this is the dictionary with fruit names and their prices
- import random # for fruit rotation randomness
- from PIL import ImageDraw # needed to draw text and stuff
- from datetime import datetime, timezone # to get current time for display
- # discord stuff (actually not used in this snippet, but prob used elsewhere in project)
- import discord
- from discord.ext import commands
- # helper func to get how big some text will be when drawn using a font
- def get_text_size(text: str, font: PIL.ImageFont.FreeTypeFont):
- dummy_img = PIL.Image.new("RGBA", (1, 1)) # just a dummy image to measure text
- draw = ImageDraw.Draw(dummy_img)
- bbox = draw.textbbox((0, 0), text, font=font)
- width = bbox[2] - bbox[0]
- height = bbox[3] - bbox[1]
- return width, height # return width and height so we can center stuff
- # helper func to get the avg color of an image by resizing it to 1x1 lol
- def get_average_color(image: PIL.Image.Image) -> tuple[int, int, int]:
- small_image = image.resize((1, 1)) # shrinking the image to 1 pixel gives avg color
- average_color = small_image.getpixel((0, 0))
- return average_color
- # makes the given color more "vibrant" by multiplying its RGB values
- def make_color_more_vibrant(color: tuple[int, int, int], factor: float = 1.3) -> tuple[int, int, int]:
- r, g, b = color
- r = min(int(r * factor), 255)
- g = min(int(g * factor), 255)
- b = min(int(b * factor), 255)
- return (r, g, b)
- # some layout constants here
- STOCK_OFFSET = 130 # how much padding left/right
- ROW_HEIGHT = 350 # vertical offset of first fruit row
- ROW_RELATIVE_OFFSET = 400 # distance between fruit rows
- FRUIT_SIZE = 240 # how big each fruit image is
- GLOW_MULTIPLIER = 3 # multiplier for glow image size
- # colors used
- GOLDEN_COLOR = (216, 185, 61)
- GREY_COLOR = (226, 226, 226)
- # the main async function to generate the stock image
- async def generate_stock_image(type: str, fruits: list[str]) -> BytesIO:
- print("RAN IMAGE STOCK") # debug log
- type = type.lower()
- if type not in ['normal', 'mirage']:
- raise ValueError("Invalid type. Must be 'normal' or 'mirage'.")
- # copy the fruit list and remove any fruits that aren't in our known values
- all_fruits = fruits.copy()
- for fruit in all_fruits:
- if fruit.lower() not in fruit_values:
- fruits.remove(fruit)
- # sort fruits based on their value
- try:
- fruits.sort(key=lambda fruit: int(fruit_values[fruit.lower()].replace(',', '')))
- except KeyError:
- raise ValueError("Invalid fruit name in the list.")
- fruit_amount = len(fruits)
- real_rows = (fruit_amount + 2) // 3 # how many actual rows needed
- calc_rows = real_rows + 1 # a bit extra to determine which background to use
- # load the background image based on how many rows we need
- background = easy_pil.Editor(f"Assets/background{real_rows}.png")
- background_image = PIL.Image.open(f"Assets/background{real_rows}.png")
- width, height = background_image.size
- width -= STOCK_OFFSET * 2 # we subtract padding from total width
- # load the fonts (all are variations of the same font)
- title_font = PIL.ImageFont.truetype("Assets/Fonts/Cinzel-ExtraBold.ttf", 115)
- NAME_FONT = PIL.ImageFont.truetype("Assets/Fonts/Cinzel-ExtraBold.ttf", 50)
- PRICE_FONT = PIL.ImageFont.truetype("Assets/Fonts/Cinzel-ExtraBold.ttf", 45)
- title_y = 80 # vertical position for the title
- # draw the titles like "Normal Stock" or "Mirage Stock"
- background.text(
- (115, title_y) if type == "normal" else (130, title_y),
- type.capitalize(),
- font=title_font,
- color=GREY_COLOR
- )
- background.text(
- (643, title_y) if type == "normal" else (625, title_y),
- "Stock",
- font=title_font,
- color=GOLDEN_COLOR
- )
- # get current UTC time and date
- now = datetime.now(timezone.utc)
- time_str = now.strftime("%H:00 (UTC)")
- date_str = now.strftime("%d/%m/%Y")
- lines = [time_str, date_str] # we’ll draw both
- DATETIME_FONT = PIL.ImageFont.truetype("Assets/Fonts/Cinzel-ExtraBold.ttf", 35)
- # calculate size of both lines
- line_widths = []
- line_heights = []
- for line in lines:
- w, h = get_text_size(line, DATETIME_FONT)
- line_widths.append(w)
- line_heights.append(h)
- max_width = max(line_widths)
- total_height = sum(line_heights) + (len(lines) - 1) * 10 # spacing between lines
- # position of the date+time text (bottom right of image)
- padding = 120
- img_width, img_height = background.image.size
- x = img_width - max_width - padding
- y = img_height - total_height - (padding + 35)
- # actually draw the lines one by one
- current_y = y
- for idx, line in enumerate(lines):
- background.text((x, current_y), line, font=DATETIME_FONT, color=GREY_COLOR)
- current_y += line_heights[idx] + 10
- # figuring out if we should center the last row (if it has 1 or 2 fruits only)
- last_row = fruit_amount % 3
- center_last_row = last_row != 0 and last_row != 3
- for i, fruit in enumerate(fruits):
- row = i // 3
- col = i % 3
- cell_width = cell_height = width // 3 # each fruit gets a third of the width
- # only apply offset on the last row if it needs centering
- if row == real_rows - 1 and center_last_row:
- offset = (3 - last_row) * (cell_width + 30) // 2
- else:
- offset = 0
- # calculate the (x, y) position for each fruit
- x = STOCK_OFFSET + col * (cell_width + 30) + offset
- y = int(ROW_HEIGHT + row * ROW_RELATIVE_OFFSET)
- rotation = random.randint(-7, 7) # small random rotation for variety
- # load fruit image, resize and rotate it
- fruit_image = PIL.Image.open(f'Assets/Fruits/{fruit.capitalize()}.png').convert("RGBA")
- fruit_image = fruit_image.resize((FRUIT_SIZE, FRUIT_SIZE))
- fruit_image = fruit_image.rotate(rotation, expand=True)
- # get average color of fruit img and make it more vibrant
- avg_color = get_average_color(fruit_image)
- vibrant_color = make_color_more_vibrant(avg_color[:3])
- # load and resize glow image (and rotate it too)
- glow_image = PIL.Image.open('Assets/glow.png').convert("RGBA")
- glow_size = FRUIT_SIZE * GLOW_MULTIPLIER
- glow_image = glow_image.resize((glow_size, glow_size))
- glow_image = glow_image.rotate(rotation, expand=True)
- # center the glow behind the fruit
- glow_offset_x = x - (glow_size - FRUIT_SIZE) // 2
- glow_offset_y = y - (glow_size - FRUIT_SIZE) // 2
- # paste glow and then fruit on top
- background.paste(glow_image, (glow_offset_x, glow_offset_y))
- background.paste(fruit_image, (x, y))
- # draw fruit name above the fruit
- fruit_name = fruit.capitalize()
- text_width, text_height = get_text_size(fruit_name, NAME_FONT)
- name_x = x + FRUIT_SIZE // 2 - text_width // 2
- name_y = y - text_height - 10
- background.text((name_x, name_y), fruit_name, font=NAME_FONT, color=vibrant_color)
- # draw price below fruit
- price_text = fruit_values.get(fruit.lower(), "0")
- dollar_width, _ = get_text_size("$", PRICE_FONT)
- price_width, _ = get_text_size(price_text, PRICE_FONT)
- total_width = dollar_width + price_width + 5
- price_x = x + FRUIT_SIZE // 2 - total_width // 2
- price_y = y + FRUIT_SIZE + 45
- background.text((price_x, price_y), "$", font=PRICE_FONT, color=GOLDEN_COLOR)
- background.text((price_x + dollar_width + 5, price_y), price_text, font=PRICE_FONT, color=GREY_COLOR)
- # finally save the image into a buffer (we return this instead of saving to disk)
- buffer = BytesIO()
- background.image.save(buffer, format="PNG")
- buffer.seek(0)
- return buffer # this can be sent over discord, for example
- # quick testing setup (will only run if this file is run directly, not imported)
- if __name__ == "__main__":
- import asyncio
- asyncio.run(generate_stock_image("normal", ["Kitsune"]))
- asyncio.run(generate_stock_image("mirage", ["Kitsune", "Kitsune", "Eagle", "Eagle", "Rubber", "Gas", "Buddha"]))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement