1
+ import os
2
+ from PIL import Image , ImageDraw , ImageFont , ImageOps
3
+
4
+ # Color code map, Source - https://www.digminecraft.com/lists/color_list_pc.php
5
+ color_code_map = {
6
+ "§4" : "#AA0000" ,
7
+ "§c" : "#FF5555" ,
8
+ "§6" : "#FFAA00" ,
9
+ "§e" : "#FFFF55" ,
10
+ "§2" : "#00AA00" ,
11
+ "§a" : "#55FF55" ,
12
+ "§b" : "#55FFFF" ,
13
+ "§3" : "#00AAAA" ,
14
+ "§1" : "#0000AA" ,
15
+ "§9" : "#5555FF" ,
16
+ "§d" : "#FF55FF" ,
17
+ "§5" : "#AA00AA" ,
18
+ "§f" : "#FFFFFF" ,
19
+ "§7" : "#AAAAAA" ,
20
+ "§8" : "#555555" ,
21
+ "§0" : "#000000" ,
22
+ "§l" : "bold" ,
23
+ "§o" : "italic" ,
24
+ "§r" : "reset" }
25
+
26
+ font_path = 'assets/fonts/Full_Minecraft-R.ttf'
27
+ offset = 60
28
+
29
+
30
+ def draw_description (desc , text_size = 100 , line_spacing = 10 , char_spacing = 5 , draw_text_start_offset_w = 0 ,
31
+ draw_text_start_offset_h = 0 ):
32
+ """
33
+ A function that generates an image out of a list of text.
34
+
35
+ :param desc: The full description as a list containing each line as a string
36
+ :type desc: list[str,]
37
+ :param text_size: The font size of the text
38
+ :type text_size: int
39
+ :param line_spacing: The spacing of each written line
40
+ :type line_spacing: int
41
+ :param char_spacing: The spacing of each written char
42
+ :type char_spacing: int
43
+ :param draw_text_start_offset_w: The offset the text would start writing from the right
44
+ :type draw_text_start_offset_w: int
45
+ :param draw_text_start_offset_h: The offset the text would start writing from the top
46
+ :type draw_text_start_offset_h: int
47
+ :return: The description box
48
+ :rtype: PIL.Image
49
+ """
50
+
51
+ # Creates an image that is bigger than the text itself so we can crop it later to the exact size of the text
52
+ template_image = Image .new ('RGBA' ,
53
+ (ImageFont .truetype (font_path ,
54
+ text_size ).getsize (max (desc , key = len ))[
55
+ 0 ] * char_spacing // 5 + offset * 2 ,
56
+ len (desc ) * line_spacing * 10 + offset * 2 ), (0 , 0 , 0 , 0 ))
57
+
58
+ # Initializes some variables
59
+ template_image_draw = ImageDraw .Draw (template_image )
60
+ current_color = color_code_map ['§7' ]
61
+ current_line_height = 30 + draw_text_start_offset_h
62
+ font = ImageFont .truetype (font_path , size = text_size )
63
+ bold = 0
64
+
65
+ # Loops through every line
66
+ for line_counter , line in enumerate (desc ):
67
+
68
+ # Checks if we've reached the end, skip the data line and break out of the loop
69
+ if line_counter != len (desc ) - 1 :
70
+ if '§7Obtained' in desc [line_counter + 1 ]:
71
+ break
72
+
73
+ # Resets the current_line_width so it can start writing again from the start
74
+ current_line_width = 30 + draw_text_start_offset_w
75
+
76
+ # Loops through every char in the line
77
+ for char_counter , char in enumerate (line ):
78
+ if char == '§' :
79
+
80
+ # If the char is not at the end
81
+ if char_counter != len (line ) - 1 :
82
+
83
+ # Set a temp variable to have the current color if that current color is NOT
84
+ # bold or reset then continue and set the actual variable to the color so it won't set
85
+ # The color to be bold or reset which are not valid colors but if they come out it will
86
+ # Make the text stroke width higher or if it's reset it will reset the font, the boldness and the
87
+ # color. After that it skips so it wont write the color code chars
88
+ tmp_current_color = color_code_map [f'§{ line [char_counter + 1 ]} ' ]
89
+ if tmp_current_color not in ['bold' , 'reset' ]:
90
+ current_color = tmp_current_color
91
+ else :
92
+ if tmp_current_color == 'bold' :
93
+ bold = 1
94
+ else :
95
+ font = ImageFont .truetype (font_path , size = text_size )
96
+ current_color = color_code_map ['§7' ]
97
+ bold = 0
98
+ continue
99
+
100
+ # If the char before the current char is § then skip writing this char because it means it is part
101
+ # of the color coding
102
+ if line [char_counter - 1 ] == '§' :
103
+ continue
104
+
105
+ # Draws the char with the right color, font and stroke width and then ups the value of the variable
106
+ # current_line_width With the current char width in pixels and with the
107
+ # char_spacing var so the chars wont overlap.
108
+ # Im drawing each char at a time so i can color each one.
109
+ template_image_draw .text ((current_line_width , current_line_height ), char , fill = current_color , font = font ,
110
+ stroke_width = bold )
111
+ current_line_width += font .getsize (char )[0 ] + char_spacing
112
+
113
+ # After each line it will up the value of the variable current_line_height with the height of the last drawn
114
+ # Char in pixels plus the line spacing so the lines wont overlap.
115
+ current_line_height += font .getsize (char )[1 ] + line_spacing
116
+
117
+ tmp_offset = offset // 2
118
+
119
+ # After all the text was drawn on the big image it will crop the image to the minimum size possible while
120
+ # keeping the text and then making it a bit larger so we would be able to put the frame in
121
+ bbox = template_image .getbbox ()
122
+ template_image = template_image .crop ((bbox [0 ] - tmp_offset , bbox [1 ] - tmp_offset ,
123
+ bbox [2 ] + tmp_offset + tmp_offset // 2 ,
124
+ bbox [3 ] + tmp_offset + tmp_offset // 2 ))
125
+
126
+ # Changes the original image's size and rotation to fit on all sides of the frame and background
127
+ frame_image = Image .new ('RGBA' , template_image .size , (0 , 0 , 0 , 0 ))
128
+ left_frame = Image .open ('assets/images/frame.png' ).resize ((tmp_offset , frame_image .size [1 ] - tmp_offset ))
129
+ right_frame = ImageOps .mirror (
130
+ Image .open ('assets/images/frame.png' ).resize ((tmp_offset , frame_image .size [1 ] - tmp_offset )))
131
+ top_frame = Image .open ('assets/images/frame.png' ).rotate (- 90 , Image .NEAREST , 1 ).resize (
132
+ (frame_image .size [0 ] - tmp_offset , tmp_offset ))
133
+ bottom_frame = Image .open ('assets/images/frame.png' ).rotate (90 , Image .NEAREST , 1 ).resize (
134
+ (frame_image .size [0 ] - tmp_offset , tmp_offset ))
135
+ background = Image .open ('assets/images/background.png' ).resize (
136
+ (frame_image .size [0 ] - offset , frame_image .size [1 ] - offset ))
137
+
138
+ # Pastes every frame, background and the text to the original image
139
+ frame_image .paste (background , (tmp_offset , tmp_offset ), background )
140
+ frame_image .paste (left_frame , (0 , tmp_offset // 2 ), left_frame )
141
+ frame_image .paste (right_frame , (frame_image .size [0 ] - tmp_offset , tmp_offset // 2 ), right_frame )
142
+ frame_image .paste (top_frame , (tmp_offset // 2 , 0 ), top_frame )
143
+ frame_image .paste (bottom_frame , (tmp_offset // 2 , frame_image .size [1 ] - tmp_offset ), bottom_frame )
144
+ frame_image .paste (template_image , (10 + draw_text_start_offset_w , 10 + draw_text_start_offset_h ), template_image )
145
+ return frame_image
146
+
147
+
148
+ draw_description (['§6Heroic Flower of Truth §6✪§6✪§6✪' , '§7Gear Score: §d726 §8(953)' , '§7Damage: §c+180 §e(+20) §8(+248.4)' , '§7Strength: §c+360 §e(+20) §9(+40) §8(+496.8)' , '§7Bonus Attack Speed: §c+5% §9(+5%) §8(+6.5%)' , '§7Intelligence: §a+100 §9(+100) §8(+138)' , ' §8[§7❁§8]' , '' , '§7§lUltimate Wise III§9, §7Cleave V§9, §7Critical V' , '§7Cubism V§9, §7Ender Slayer V§9, §7Execute V' , '§9Experience III§9, §7First Strike IV§9, §7Giant Killer V' , '§7Impaling III§9, §7Lethality V§9, §7Life Steal III' , '§9Looting III§9, §7Luck V§9, §9Scavenger III' , '§9Sharpness V§9, §9Telekinesis I§9, §7Thunderbolt V' , '§7Vampirism V§9, §7Venomous V' , '' , '§7§cYou do not have a high enough' , '§cEnchanting level to use some of' , '§cthe enchantments on this item!' , '' , '§6Ability: Heat-Seeking Rose §e§lRIGHT CLICK' , '§7Shoots a rose that ricochets' , '§7between enemies, damaging up to' , '§7§a3 §7of your foes! Damage' , '§7multiplies as more enemies are' , '§7hit.' , '§8Mana Cost: §37' , '§8Cooldown: §a1s' , '§7§7The mana cost of this item is' , '§7§a10.0% §7of your maximum mana.' , '' , '§aPerfect 70000 / 70000' , '§6§lLEGENDARY DUNGEON SWORD' , '' , '§7Obtained: §c<local-time timestamp="1627124400000"></local-time>' ]).save ('test.png' )
0 commit comments