212 lines
7.7 KiB
Python
212 lines
7.7 KiB
Python
from PIL import Image, ImageDraw, ImageFont
|
|
import requests, os
|
|
from time import sleep
|
|
|
|
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/'
|
|
}
|
|
|
|
for i in range(3):
|
|
response = requests.get(f"{DNDB_BASE_URL}/{char['characterId']}/json", headers=headers)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
print(data["stats"])
|
|
break
|
|
sleep(1)
|
|
|
|
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)
|