from PIL import Image, ImageDraw, ImageFont import requests, os DNDB_BASE_URL = "https://www.dndbeyond.com/character/" DNDB_CHARS = [ { "characterId": 69760590, "characterName": "Вевель", "characterClass": "острожник 10", "portraitName": "vevel", "characterUrl": "/profile/megavenik/characters/69760590", "avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/654/150/154/1581111423-69760590.jpeg", }, { "characterId": 69976266, "characterName": "Милош", "characterClass": "помазанник", "portraitName": "milosh", "characterUrl": "/profile/EdgarBrin/characters/69976266", "avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/725/150/155/1581111423-69976266.jpeg", }, { "characterId": 69976392, "characterName": "Милица", "characterClass": "голос", "portraitName": "militsa", "characterUrl": "/profile/tritiumTino/characters/69976392", "avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/709/150/154/1581111423-69976392.jpeg", }, { "characterId": 90463225, "characterName": "Нивенна", "characterClass": "чаровница", "portraitName": "nivenna", "characterUrl": "/profile/Maslinium/characters/90463225", "avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/748/160/150/1581111423-90463225.jpeg", }, { "characterId": 99072742, "characterName": "Дарпа", "characterClass": "кухарь", "portraitName": "darpa", "characterUrl": "/profile/Forfattare/characters/99072742", "avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/34003/953/150/153/1581111423-99072742.jpeg", }, { "characterId": 99483409, "characterName": "Деян", "characterClass": "", "portraitName": "deyan", "characterUrl": "/profile/SantaKosh/characters/99483409", "avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/34005/197/157/150/1581111423-99483409.jpeg", } ] class DataModifierService: def getModifiersByType(self, groupedModifiers: dict, type: str, subType: str = "") -> list: matching = [] for __, modifiers in groupedModifiers.items(): filtered = list(filter(lambda item: 'type' in item and type == item['type'], modifiers)) if subType: filtered = list(filter(lambda item: 'subType' in item and subType == item['subType'], filtered)) matching += filtered return matching def getTotalModifierValue(self, groupedModifiers: dict, type: str, subType: str) -> int: modifiers = self.getModifiersByType(groupedModifiers, type, subType) modifier_values = [modifier['value'] for modifier in modifiers] return sum(modifier_values) def get_stat_modifier(stat): import math return math.floor((stat - 10) / 2) def calculate_coordinates(object_width, object_height, center_point): """ Calculates the coordinates to put the center point of an object at a specified point. Args: object_width (int): The width of the object. object_height (int): The height of the object. center_point (tuple): The (x, y) coordinates of the point where the center of the object should be placed. Returns: tuple: The (x, y) coordinates of the top-left corner of the object. """ x = center_point[0] - object_width // 2 y = center_point[1] - object_height // 2 return (x, y) def get_correct_text_sizes(draw, text: str, font: ImageFont.FreeTypeFont): label_width, label_height = draw.textsize(text, font=font) # to fix text placing we need to get offsets # https://stackoverflow.com/questions/59008322/pillow-imagedraw-text-coordinates-to-center offset_x, offset_y = font.getoffset(text) label_width += offset_x label_height += offset_y return (label_width, label_height) def generate_main(char: dict): STATS_DICT = { 1: { "name": "strength", "coords": (684, 304) }, 2: { "name": "dexterity", "coords": (684, 381) }, 3: { "name": "constitution", "coords": (684, 457) }, 4: { "name": "intelligence", "coords": (767, 304) }, 5: { "name": "wisdom", "coords": (767, 381) }, 6: { "name": "charisma", "coords": (767, 457) }, } headers = { 'Content-Type': 'text/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0', 'Referer': 'https://www.dndbeyond.com/' } response = requests.get(f"{DNDB_BASE_URL}/{char['characterId']}/json", headers=headers) if response.status_code == 200: data = response.json() print(data["stats"]) else: print("Error: Failed to retrieve data from URL.") return False # Load the background image background = Image.open("parts/background-main.png") # Load the image to be placed on top foreground = Image.open(f"parts/{char['portraitName']}-main.png") # Paste the foreground image onto the background background.paste(foreground, (0, 0), foreground) # Create a drawing context draw = ImageDraw.Draw(background) draw_width, _ = draw.im.size # pyright: ignore[reportGeneralTypeIssues] for stat in data["stats"]: modifiers = data.get('modifiers', {}) modifier_service = DataModifierService() total_modifier_value = modifier_service.getTotalModifierValue(modifiers, 'bonus', STATS_DICT[stat['id']]['name'] + '-score') stat["value"] += total_modifier_value final_stat = get_stat_modifier(stat["value"]) # Define the font and font size for the text labels stats_font = ImageFont.truetype("fonts/NotoSerif-Regular.ttf", 30) # Add text labels on top of the image stats_label = f"+{final_stat}" if final_stat > 0 else f"{final_stat}" print(f"{STATS_DICT[stat['id']]['name']}: {stats_label}") label_width, label_height = get_correct_text_sizes(draw, stats_label, stats_font) draw.text(calculate_coordinates(label_width, label_height, STATS_DICT[stat["id"]]["coords"]), stats_label, font=stats_font, fill=(255, 255, 255)) name_font = ImageFont.truetype("fonts/alaruss.ttf", 96) name_label = char['characterName'].upper() label_width, label_height = get_correct_text_sizes(draw, name_label, name_font) draw.text((draw_width-label_width-20, 120), name_label, font=name_font, fill=(255,255,255)) class_font = ImageFont.truetype("fonts/NotoSerif-Regular.ttf", 32) class_label = char['characterClass'].lower() label_width, label_height = get_correct_text_sizes(draw, class_label, class_font) draw.text((draw_width-label_width-15, 203), class_label, font=class_font, fill=(255,255,255)) results_dir = "results" if not os.path.exists(results_dir): os.makedirs(results_dir) background.save(f"results/{char['portraitName']}-main.png") return True # Save the resulting image generate_main(DNDB_CHARS[0]) # for char in DNDB_CHARS: # generate_main(char)