TaikoPythonTools/TaikoWiiUSongTextureTool/generate.py

490 lines
19 KiB
Python
Raw Normal View History

2024-06-19 00:23:51 +02:00
import os
import sys
import json
from PIL import Image, ImageDraw, ImageFont
# Define a dictionary for vertical forms of certain punctuation marks
rotated_chars = {
'': '', '': '',
'': '', '': '',
'': '', '': '',
2024-06-24 00:33:59 +02:00
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '︿', '': '',
'': '', '': '',
'': '︿', '': '',
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '', '': '',
'': '︿', '': '',
'': '', '': '',
'': '', '': '',
2024-06-27 11:29:03 +02:00
'': '', '': '',
2024-07-05 21:00:26 +02:00
'': '', '': '',
'(': '', ')': '',
'-': 'l'
2024-06-27 11:29:03 +02:00
}
rotated_letters = {
'': ''
}
full_width_chars = {
'': 'A', '': 'B', '': 'C', '': 'D', '': 'E', '': 'F', '': 'G', '': 'H', '': 'I',
'': 'J', '': 'K', '': 'L', '': 'M', '': 'N', '': 'O', '': 'P', '': 'Q', '': 'R',
'': 'S', '': 'T', '': 'U', '': 'V', '': 'W', '': 'X', '': 'Y', '': 'Z',
'': 'a', '': 'b', '': 'c', '': 'd', '': 'e', '': 'f', '': 'g', '': 'h', '': 'i',
'': 'j', '': 'k', '': 'l', '': 'm', '': 'n', '': 'o', '': 'p', '': 'q', '': 'r',
'': 's', '': 't', '': 'u', '': 'v', '': 'w', '': 'x', '': 'y', '': 'z'
2024-06-19 00:23:51 +02:00
}
2024-06-27 11:29:03 +02:00
def convert_full_width(text):
converted_text = ''
for char in text:
converted_text += full_width_chars.get(char, char)
return converted_text
2024-06-24 00:33:59 +02:00
2024-06-19 00:23:51 +02:00
def get_text_bbox(draw, text, font):
return draw.textbbox((0, 0), text, font=font)
def generate_image(draw, text, font, rotated_font, size, position, alignment, stroke_width, stroke_fill, fill, vertical=False, vertical_small=False):
width, height = size
# Calculate initial text dimensions
text_bbox = get_text_bbox(draw, text, font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
if vertical or vertical_small:
text_height = 0
max_char_width = 0
for char in text:
char_font = rotated_font if char in rotated_chars else font
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-19 00:23:51 +02:00
text_bbox = get_text_bbox(draw, char, char_font)
text_height += text_bbox[3] - text_bbox[1]
char_width = text_bbox[2] - text_bbox[0]
if char_width > max_char_width:
max_char_width = char_width
text_height = max(0, text_height - 1) # Remove the last extra space
text_position = (position[0] - max_char_width / 2, (height - text_height) / 2)
else:
text_bbox = get_text_bbox(draw, text, font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
if alignment == 'center':
text_position = ((width - text_width) / 2, position[1])
elif alignment == 'right':
text_position = (width - text_width, position[1])
else:
text_position = position
if vertical:
y_offset = 5
for char in text:
char_font = rotated_font if char in rotated_chars else font
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-19 00:23:51 +02:00
text_bbox = get_text_bbox(draw, char, char_font)
2024-07-05 21:00:26 +02:00
char_height = (text_bbox[3] + text_bbox[1])
2024-06-19 00:23:51 +02:00
char_width = text_bbox[2] - text_bbox[0]
draw.text((text_position[0] - char_width / 2, y_offset), char, font=char_font, fill=fill, stroke_width=stroke_width, stroke_fill=stroke_fill)
y_offset += char_height
elif vertical_small:
2024-06-24 00:33:59 +02:00
y_offset = 5
2024-06-19 00:23:51 +02:00
for char in text:
char_font = rotated_font if char in rotated_chars else font
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-19 00:23:51 +02:00
text_bbox = get_text_bbox(draw, char, char_font)
2024-07-05 21:00:26 +02:00
char_height = (text_bbox[3] + text_bbox[1])
2024-06-19 00:23:51 +02:00
char_width = text_bbox[2] - text_bbox[0]
draw.text((text_position[0] - char_width / 2, y_offset), char, font=char_font, fill=fill, stroke_width=stroke_width, stroke_fill=stroke_fill)
y_offset += char_height
else:
draw.text(text_position, text, font=font, fill=fill, stroke_width=stroke_width, stroke_fill=stroke_fill)
def create_images(data, id, genreNo, font_path, rotated_font_path, append_ura=False):
2024-06-19 00:23:51 +02:00
font_size_extra_large = 46.06875
font_size_large = 40.60875
font_size_medium = 27.3
font_size_small = 21.84
2024-06-24 00:33:59 +02:00
img_3_5_height = 400
2024-06-19 00:23:51 +02:00
folder_name = id
os.makedirs(folder_name, exist_ok=True)
# Define genre colors
genre_colors = [
(0, 78, 88), # pop
(159, 61, 2), # anime
(90, 98, 129), # vocaloid
(55, 74, 0), # variety
(0, 0, 0), # unused (kids)
(115, 77, 0), # classic
(82, 32, 115), # game music
(156, 36, 8), # namco original
]
genre_color = genre_colors[genreNo]
# Initialize text variables
japanese_text = ""
japanese_sub_text = ""
2024-06-27 11:29:03 +02:00
2024-06-19 00:23:51 +02:00
# Find the relevant texts
for item in data['items']:
if item['key'] == f'song_{id}':
japanese_text = item['japaneseText']
if item['key'] == f'song_sub_{id}':
japanese_sub_text = item['japaneseText']
2024-06-27 11:29:03 +02:00
# Convert full-width English characters to normal ASCII characters
japanese_text = convert_full_width(japanese_text)
japanese_sub_text = convert_full_width(japanese_sub_text) if japanese_sub_text else ''
# Append "─" character if -ura argument is provided
if append_ura:
japanese_text += ""
2024-07-05 21:00:26 +02:00
#japanese_text = " " + japanese_text
#japanese_text += " "
padded_japanese_text = japanese_text + " "
even_more_padded_japanese_text = " " + japanese_text
padded_japanese_sub_text = japanese_sub_text + " "
2024-06-27 23:21:03 +02:00
2024-06-19 00:23:51 +02:00
# Check if texts were found
if not japanese_text:
print(f"Error: No Japanese text found for song_{id}")
return
if not japanese_sub_text:
print(f"Warning: No Japanese sub text found for song_sub_{id}")
font_extra_large = ImageFont.truetype(font_path, int(font_size_extra_large))
font_large = ImageFont.truetype(font_path, int(font_size_large))
font_medium = ImageFont.truetype(font_path, int(font_size_medium))
font_small = ImageFont.truetype(font_path, int(font_size_small))
rotated_font = ImageFont.truetype(rotated_font_path, int(font_size_medium))
# Image 0.png
2024-07-05 21:00:26 +02:00
img0_width = 720
img0 = Image.new('RGBA', (img0_width, 64), color=(0, 0, 0, 0))
2024-06-19 00:23:51 +02:00
draw0 = ImageDraw.Draw(img0)
2024-07-05 21:00:26 +02:00
temp_img0 = Image.new('RGBA', (2880, 64), (0, 0, 0, 0)) # Temporary image with 2880px width
temp_draw0 = ImageDraw.Draw(temp_img0)
# Generate the image with the Japanese text
generate_image(temp_draw0, even_more_padded_japanese_text, font_large, rotated_font, (2880, 64), (0, 10), 'right', 5, 'black', 'white')
# Calculate the bounding box of the entire text
text_bbox = get_text_bbox(temp_draw0, japanese_text, font_large)
text_width = (text_bbox[2] - text_bbox[0]) + 5
# Resize the image if it exceeds the specified height
if text_width > img0_width:
cropped_img0 = temp_img0.crop((2880 - text_width, 0, 2880, 64))
scaled_img0 = cropped_img0.resize((img0_width, 64), Image.Resampling.LANCZOS)
final_img0 = Image.new('RGBA', (img0_width, 64), (0, 0, 0, 0))
final_img0.paste(scaled_img0)
else:
# Crop the temporary image to the actual width of the text
cropped_img0 = temp_img0.crop((2880 - text_width, 0, 2880, 64))
final_img0 = Image.new('RGBA', (img0_width, 64), (0, 0, 0, 0))
final_img0.paste(cropped_img0, (img0_width - text_width, 0))
# Create a new image with the specified width and right-align the text
#final_img0 = Image.new('RGBA', (img0_width, 64), (0, 0, 0, 0))
#final_img0.paste(cropped_img, (img0_width - text_width, 0))
# Save the final image
final_img0.save(os.path.join(folder_name, '0.png'))
2024-06-19 00:23:51 +02:00
# Image 1.png
2024-07-05 21:00:26 +02:00
img1_width = 720
img1 = Image.new('RGBA', (img1_width, 104), color=(0, 0, 0, 0))
2024-06-19 00:23:51 +02:00
draw1 = ImageDraw.Draw(img1)
2024-07-05 21:00:26 +02:00
temp_img1 = Image.new('RGBA', (2880, 104), (0, 0, 0, 0)) # Temporary image with 2880px width
temp_draw1 = ImageDraw.Draw(temp_img1)
temp_sub_img1 = Image.new('RGBA', (2880, 104), (0, 0, 0, 0)) # Temporary image with 2880px width
temp_sub_draw1 = ImageDraw.Draw(temp_sub_img1)
# Generate the image with the Japanese text
generate_image(temp_draw1, japanese_text, font_extra_large, rotated_font, (2880, 104), (0, 13), 'center', 5, 'black', 'white')
# Calculate the bounding box of the entire text
text_bbox = get_text_bbox(temp_draw1, japanese_text, font_extra_large)
text_width = (text_bbox[2] - text_bbox[0]) + 5
# Resize the image if it exceeds the specified width
if text_width > img1_width:
# Calculate the crop box to crop equally from both sides
left_crop = (2880 - text_width) // 2
right_crop = 2880 - left_crop
cropped_img1 = temp_img1.crop((left_crop, 0, right_crop, 104))
scaled_img1 = cropped_img1.resize((img1_width, 104), Image.Resampling.LANCZOS)
img1_1 = Image.new('RGBA', (img1_width, 104), (0, 0, 0, 0))
img1_1.paste(scaled_img1)
else:
# Crop the temporary image to the actual width of the text
left_crop = (2880 - text_width) // 2
right_crop = 2880 - left_crop
cropped_img1 = temp_img1.crop((left_crop, 0, right_crop, 104))
img1_1 = Image.new('RGBA', (img1_width, 104), (0, 0, 0, 0))
offset = (img1_width - text_width) // 2
img1_1.paste(cropped_img1, (offset, 0))
# Generate the image with the Japanese sub-text
generate_image(temp_sub_draw1, japanese_sub_text, font_medium, rotated_font, (2880, 104), (0, 68), 'center', 4, 'black', 'white')
# Calculate the bounding box of the entire sub-text
text_bbox_sub = get_text_bbox(temp_sub_draw1, japanese_sub_text, font_medium)
text_width_sub = (text_bbox_sub[2] - text_bbox_sub[0]) + 5
# Resize the sub-image if it exceeds the specified width
if text_width_sub > img1_width:
# Calculate the crop box to crop equally from both sides
left_crop_sub = (2880 - text_width_sub) // 2
right_crop_sub = 2880 - left_crop_sub
cropped_img1_sub = temp_sub_img1.crop((left_crop_sub, 0, right_crop_sub, 104))
scaled_img1_sub = cropped_img1_sub.resize((img1_width, 104), Image.Resampling.LANCZOS)
img1_2 = Image.new('RGBA', (img1_width, 104), (0, 0, 0, 0))
img1_2.paste(scaled_img1_sub)
else:
# Crop the temporary sub-image to the actual width of the sub-text
left_crop_sub = (2880 - text_width_sub) // 2
right_crop_sub = 2880 - left_crop_sub
cropped_img1_sub = temp_sub_img1.crop((left_crop_sub, 0, right_crop_sub, 104))
img1_2 = Image.new('RGBA', (img1_width, 104), (0, 0, 0, 0))
offset_sub = (img1_width - text_width_sub) // 2
img1_2.paste(cropped_img1_sub, (offset_sub, 0))
final_img1 = Image.new('RGBA', (img1_width, 104), (0, 0, 0, 0))
final_img1.paste(img1_1, (0, 0))
final_img1.paste(img1_2, (0, 0), img1_2)
final_img1.save(os.path.join(folder_name, '1.png'))
2024-06-19 00:23:51 +02:00
# Image 2.png
2024-07-05 21:00:26 +02:00
img2_width = 720
img2 = Image.new('RGBA', (img2_width, 64), color=(0, 0, 0, 0))
2024-06-19 00:23:51 +02:00
draw2 = ImageDraw.Draw(img2)
2024-07-05 21:00:26 +02:00
temp_img2 = Image.new('RGBA', (2880, 64), (0, 0, 0, 0)) # Temporary image with 2880px width
temp_draw2 = ImageDraw.Draw(temp_img2)
# Generate the image with the Japanese text
generate_image(temp_draw2, japanese_text, font_large, rotated_font, (2880, 64), (0, 4), 'center', 5, 'black', 'white')
# Calculate the bounding box of the entire text
text_bbox = get_text_bbox(temp_draw2, japanese_text, font_large)
text_width = (text_bbox[2] - text_bbox[0]) + 5
# Resize the image if it exceeds the specified height
if text_width > img2_width:
# Calculate the crop box to crop equally from both sides
left_crop = (2880 - text_width) // 2
right_crop = 2880 - left_crop
cropped_img2 = temp_img2.crop((left_crop, 0, right_crop, 64))
scaled_img2 = cropped_img2.resize((img2_width, 64), Image.Resampling.LANCZOS)
final_img2 = Image.new('RGBA', (img2_width, 64), (0, 0, 0, 0))
final_img2.paste(scaled_img2)
else:
# Crop the temporary image to the actual width of the text
left_crop = (2880 - text_width) // 2
right_crop = 2880 - left_crop
cropped_img2 = temp_img2.crop((left_crop, 0, right_crop, 64))
final_img2 = Image.new('RGBA', (img2_width, 64), (0, 0, 0, 0))
offset = (img2_width - text_width) // 2
final_img2.paste(cropped_img2, (offset, 0))
final_img2.save(os.path.join(folder_name, '2.png'))
2024-06-24 00:33:59 +02:00
# Image 3.png
img3_height = 400
2024-06-19 00:23:51 +02:00
img3 = Image.new('RGBA', (96, 400), color=(0, 0, 0, 0))
2024-06-24 00:33:59 +02:00
img3_1 = Image.new('RGBA', (96, 400), color=(0, 0, 0, 0))
img3_2 = Image.new('RGBA', (96, 400), color=(0, 0, 0, 0))
2024-06-19 00:23:51 +02:00
draw3 = ImageDraw.Draw(img3)
2024-06-24 00:33:59 +02:00
2024-07-05 21:00:26 +02:00
temp_img3 = Image.new('RGBA', (96, 3000), (0, 0, 0, 0)) # Temporary image with 1000px height
2024-06-24 00:33:59 +02:00
temp_draw3 = ImageDraw.Draw(temp_img3)
2024-07-05 21:00:26 +02:00
temp_sub_img3 = Image.new('RGBA', (96, 3000), (0, 0, 0, 0)) # Temporary image with 1000px height
2024-06-24 00:33:59 +02:00
temp_sub_draw3 = ImageDraw.Draw(temp_sub_img3)
2024-07-05 21:00:26 +02:00
generate_image(temp_draw3, padded_japanese_text, font_large, rotated_font, (96, 3000), (89, 0), 'center', 5, 'black', 'white', vertical=True)
2024-06-24 00:33:59 +02:00
# Crop the temporary image to the actual height of the text
y_offset = 0
2024-07-05 21:00:26 +02:00
for char in padded_japanese_text:
2024-06-24 00:33:59 +02:00
char_font = rotated_font if char in rotated_chars else font_large
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-24 00:33:59 +02:00
text_bbox = get_text_bbox(temp_draw3, char, char_font)
2024-07-05 21:00:26 +02:00
char_height = (text_bbox[3] + text_bbox[1])
2024-06-24 00:33:59 +02:00
y_offset += char_height
# Crop the temporary image to the actual height of the text
temp_img3 = temp_img3.crop((0, 0, 96, y_offset))
# Resize the image if it exceeds the specified height
if y_offset > img3_height:
img3_1 = temp_img3.resize((96, img3_height), Image.Resampling.LANCZOS)
else:
img3_1 = temp_img3.crop((0, 0, 96, img3_height))
2024-07-05 21:00:26 +02:00
generate_image(temp_sub_draw3, japanese_sub_text, font_medium, rotated_font, (96, 3000), (32, 156), 'center', 4, 'black', 'white', vertical_small=True)
2024-06-24 00:33:59 +02:00
# Crop the temporary image to the actual height of the text
y_offset = 0
for char in japanese_sub_text:
2024-07-05 21:00:26 +02:00
char_font = rotated_font if char in rotated_chars else font_medium
2024-06-24 00:33:59 +02:00
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-24 00:33:59 +02:00
text_bbox = get_text_bbox(temp_sub_draw3, char, char_font)
2024-07-05 21:00:26 +02:00
char_height = round((text_bbox[3] + text_bbox[1]) * 1.1)
2024-06-24 00:33:59 +02:00
y_offset += char_height
# Crop the temporary image to the actual height of the text
temp_sub_img3 = temp_sub_img3.crop((0, 0, 96, y_offset))
# Resize the image if it exceeds the specified height
if y_offset > img3_height:
img3_2 = temp_sub_img3.resize((96, img3_height), Image.Resampling.LANCZOS)
else:
img3_2 = temp_sub_img3.crop((0, 0, 96, img3_height))
img3.paste(img3_1, (0, 0))
img3.paste(img3_2, (0, 0), img3_2)
2024-06-19 00:23:51 +02:00
img3.save(os.path.join(folder_name, '3.png'))
# Image 4.png
2024-06-24 00:33:59 +02:00
img4_height = 400
2024-06-19 00:23:51 +02:00
img4 = Image.new('RGBA', (56, 400), color=(0, 0, 0, 0))
draw4 = ImageDraw.Draw(img4)
2024-06-24 00:33:59 +02:00
2024-07-05 21:00:26 +02:00
temp_img4 = Image.new('RGBA', (56, 3000), (0, 0, 0, 0)) # Temporary image with 3000px height
2024-06-24 00:33:59 +02:00
temp_draw4 = ImageDraw.Draw(temp_img4)
2024-07-05 21:00:26 +02:00
generate_image(temp_draw4, padded_japanese_text, font_large, rotated_font, (56, 400), (48, 0), 'center', 5, genre_color, 'white', vertical=True)
2024-06-24 00:33:59 +02:00
# Crop the temporary image to the actual height of the text
y_offset = 0
2024-07-05 21:00:26 +02:00
for char in padded_japanese_text:
2024-06-24 00:33:59 +02:00
char_font = rotated_font if char in rotated_chars else font_large
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-24 00:33:59 +02:00
text_bbox = get_text_bbox(temp_draw4, char, char_font)
2024-07-05 21:00:26 +02:00
char_height = (text_bbox[3] + text_bbox[1])
2024-06-24 00:33:59 +02:00
y_offset += char_height
# Crop the temporary image to the actual height of the text
temp_img4 = temp_img4.crop((0, 0, 56, y_offset))
# Resize the image if it exceeds the specified height
if y_offset > img4_height:
img4 = temp_img4.resize((56, img4_height), Image.Resampling.LANCZOS)
else:
img4 = temp_img4.crop((0, 0, 56, img4_height))
2024-06-19 00:23:51 +02:00
img4.save(os.path.join(folder_name, '4.png'))
# Image 5.png
2024-06-24 00:33:59 +02:00
img5_height = 400
2024-06-19 00:23:51 +02:00
img5 = Image.new('RGBA', (56, 400), color=(0, 0, 0, 0))
draw5 = ImageDraw.Draw(img5)
2024-06-24 00:33:59 +02:00
2024-07-05 21:00:26 +02:00
temp_img5 = Image.new('RGBA', (56, 3000), (0, 0, 0, 0)) # Temporary image with 1000px height
2024-06-24 00:33:59 +02:00
temp_draw5 = ImageDraw.Draw(temp_img5)
2024-07-05 21:00:26 +02:00
generate_image(temp_draw5, padded_japanese_text, font_large, rotated_font, (56, 400), (48, 0), 'center', 5, 'black', 'white', vertical=True)
2024-06-24 00:33:59 +02:00
# Crop the temporary image to the actual height of the text
y_offset = 0
2024-07-05 21:00:26 +02:00
for char in padded_japanese_text:
2024-06-24 00:33:59 +02:00
char_font = rotated_font if char in rotated_chars else font_large
char = rotated_chars.get(char, char)
2024-06-27 11:29:03 +02:00
char = rotated_letters.get(char, char)
2024-06-24 00:33:59 +02:00
text_bbox = get_text_bbox(temp_draw5, char, char_font)
2024-07-05 21:00:26 +02:00
char_height = (text_bbox[3] + text_bbox[1])
2024-06-24 00:33:59 +02:00
y_offset += char_height
# Crop the temporary image to the actual height of the text
temp_img5 = temp_img5.crop((0, 0, 56, y_offset))
# Resize the image if it exceeds the specified height
if y_offset > img5_height:
img5 = temp_img5.resize((56, img5_height), Image.Resampling.LANCZOS)
else:
img5 = temp_img5.crop((0, 0, 56, img5_height))
2024-06-19 00:23:51 +02:00
img5.save(os.path.join(folder_name, '5.png'))
2024-06-24 00:33:59 +02:00
2024-06-19 00:23:51 +02:00
if __name__ == "__main__":
if len(sys.argv) < 3 or len(sys.argv) > 4:
print("Usage: generate.py <id> <genreNo> [-ura]")
2024-06-19 00:23:51 +02:00
sys.exit(1)
id = sys.argv[1]
try:
genreNo = int(sys.argv[2])
if genreNo < 0 or genreNo >= 8:
2024-06-19 00:23:51 +02:00
raise ValueError
except ValueError:
print("Error: genreNo must be an integer between 0 and 7")
sys.exit(1)
if len(sys.argv) == 4 and sys.argv[3] == "-ura":
append_ura = True
else:
append_ura = False
2024-06-19 00:23:51 +02:00
wordlist_path = 'resources/wordlist.json'
font_path = 'resources/DFPKanTeiRyu-XB.ttf'
2024-06-24 00:33:59 +02:00
rotated_font_path = 'resources/KozGoPr6NRegular.otf'
2024-06-19 00:23:51 +02:00
if not os.path.isfile(wordlist_path):
print(f"Error: {wordlist_path} not found")
sys.exit(1)
if not os.path.isfile(font_path):
print(f"Error: {font_path} not found")
sys.exit(1)
if not os.path.isfile(rotated_font_path):
print(f"Error: {rotated_font_path} not found")
sys.exit(1)
with open(wordlist_path, 'r', encoding='utf-8') as f:
data = json.load(f)
create_images(data, id, genreNo, font_path, rotated_font_path, append_ura)