|
1 | | -""" |
2 | | -Generate a full profile image with customizable parameters. |
3 | | -If the avatar is animated and not the background, the avatar will be rendered as a gif. |
4 | | -If the background is animated and not the avatar, the background will be rendered as a gif. |
5 | | -If both are animated, the avatar will be rendered as a gif and the background will be rendered as a static image. |
6 | | -To optimize performance, the profile will be generated in 3 layers, the background, the avatar, and the stats. |
7 | | -The stats layer will be generated as a separate image and then pasted onto the background. |
8 | | -
|
9 | | -Args: |
10 | | - background (t.Optional[bytes], optional): The background image as bytes. Defaults to None. |
11 | | - avatar (t.Optional[bytes], optional): The avatar image as bytes. Defaults to None. |
12 | | - username (t.Optional[str], optional): The username. Defaults to "Spartan117". |
13 | | - status (t.Optional[str], optional): The status. Defaults to "online". |
14 | | - level (t.Optional[int], optional): The level. Defaults to 1. |
15 | | - messages (t.Optional[int], optional): The number of messages. Defaults to 0. |
16 | | - voicetime (t.Optional[int], optional): The voicetime. Defaults to 3600. |
17 | | - stars (t.Optional[int], optional): The number of stars. Defaults to 0. |
18 | | - prestige (t.Optional[int], optional): The prestige level. Defaults to 0. |
19 | | - prestige_emoji (t.Optional[bytes], optional): The prestige emoji as bytes. Defaults to None. |
20 | | - balance (t.Optional[int], optional): The balance. Defaults to 0. |
21 | | - currency_name (t.Optional[str], optional): The name of the currency. Defaults to "Credits". |
22 | | - previous_xp (t.Optional[int], optional): The previous XP. Defaults to 0. |
23 | | - current_xp (t.Optional[int], optional): The current XP. Defaults to 0. |
24 | | - next_xp (t.Optional[int], optional): The next XP. Defaults to 0. |
25 | | - position (t.Optional[int], optional): The position. Defaults to 0. |
26 | | - role_icon (t.Optional[bytes, str], optional): The role icon as bytes or url. Defaults to None. |
27 | | - blur (t.Optional[bool], optional): Whether to blur the box behind the stats. Defaults to False. |
28 | | - user_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the user. Defaults to None. |
29 | | - base_color (t.Optional[t.Tuple[int, int, int]], optional): The base color. Defaults to None. |
30 | | - stat_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the stats. Defaults to None. |
31 | | - level_bar_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the level bar. Defaults to None. |
32 | | - font_path (t.Optional[t.Union[str, Path], optional): The path to the font file. Defaults to None. |
33 | | - render_gif (t.Optional[bool], optional): Whether to render as gif if profile or background is one. Defaults to False. |
34 | | - debug (t.Optional[bool], optional): Whether to raise any errors rather than suppressing. Defaults to False. |
35 | | -
|
36 | | -Returns: |
37 | | - t.Tuple[bytes, bool]: The generated full profile image as bytes, and whether the image is animated. |
38 | | -""" |
39 | | - |
| 1 | +import importlib.util |
40 | 2 | import logging |
41 | 3 | import math |
| 4 | +import sys |
42 | 5 | import typing as t |
43 | 6 | from io import BytesIO |
44 | 7 | from pathlib import Path |
|
48 | 11 | from redbot.core.utils.chat_formatting import humanize_number |
49 | 12 |
|
50 | 13 | try: |
| 14 | + # Loaded from cog |
51 | 15 | from .. import imgtools |
52 | 16 | from ..pilmojisrc.core import Pilmoji |
53 | 17 | except ImportError: |
54 | | - import imgtools |
55 | | - from pilmojisrc.core import Pilmoji |
| 18 | + # Running in vscode "Run Python File in Terminal" |
| 19 | + # Add parent directory to sys.path to enable imports |
| 20 | + parent_dir = Path(__file__).parent.parent |
| 21 | + sys.path.insert(0, str(parent_dir)) |
| 22 | + |
| 23 | + # Import imgtools directly |
| 24 | + imgtools_path = parent_dir / "imgtools.py" |
| 25 | + if imgtools_path.exists(): |
| 26 | + spec = importlib.util.spec_from_file_location("imgtools", imgtools_path) |
| 27 | + imgtools = importlib.util.module_from_spec(spec) |
| 28 | + sys.modules["imgtools"] = imgtools |
| 29 | + spec.loader.exec_module(imgtools) |
| 30 | + else: |
| 31 | + raise ImportError(f"Could not find imgtools at {imgtools_path}") |
| 32 | + |
| 33 | + # Set up pilmojisrc as a package |
| 34 | + pilmoji_dir = parent_dir / "pilmojisrc" |
| 35 | + if not pilmoji_dir.exists(): |
| 36 | + raise ImportError(f"Could not find pilmojisrc directory at {pilmoji_dir}") |
| 37 | + |
| 38 | + # Create and register the pilmojisrc package |
| 39 | + pilmojisrc_init = pilmoji_dir / "__init__.py" |
| 40 | + if pilmojisrc_init.exists(): |
| 41 | + spec = importlib.util.spec_from_file_location("pilmojisrc", pilmojisrc_init) |
| 42 | + pilmojisrc = importlib.util.module_from_spec(spec) |
| 43 | + sys.modules["pilmojisrc"] = pilmojisrc |
| 44 | + spec.loader.exec_module(pilmojisrc) |
| 45 | + else: |
| 46 | + # Create an empty module if __init__.py doesn't exist |
| 47 | + pilmojisrc = type(sys)("pilmojisrc") |
| 48 | + sys.modules["pilmojisrc"] = pilmojisrc |
| 49 | + |
| 50 | + # Import helpers module first (since core depends on it) |
| 51 | + helpers_path = pilmoji_dir / "helpers.py" |
| 52 | + if helpers_path.exists(): |
| 53 | + spec = importlib.util.spec_from_file_location("pilmojisrc.helpers", helpers_path) |
| 54 | + helpers = importlib.util.module_from_spec(spec) |
| 55 | + pilmojisrc.helpers = helpers |
| 56 | + sys.modules["pilmojisrc.helpers"] = helpers |
| 57 | + spec.loader.exec_module(helpers) |
| 58 | + else: |
| 59 | + raise ImportError(f"Could not find helpers module at {helpers_path}") |
| 60 | + |
| 61 | + # Now import core module |
| 62 | + core_path = pilmoji_dir / "core.py" |
| 63 | + if core_path.exists(): |
| 64 | + spec = importlib.util.spec_from_file_location("pilmojisrc.core", core_path) |
| 65 | + core = importlib.util.module_from_spec(spec) |
| 66 | + pilmojisrc.core = core |
| 67 | + sys.modules["pilmojisrc.core"] = core |
| 68 | + spec.loader.exec_module(core) |
| 69 | + Pilmoji = core.Pilmoji |
| 70 | + else: |
| 71 | + raise ImportError(f"Could not find core module at {core_path}") |
| 72 | + |
56 | 73 |
|
57 | 74 | log = logging.getLogger("red.vrt.levelup.generator.styles.default") |
58 | 75 | _ = Translator("LevelUp", __file__) |
@@ -85,8 +102,50 @@ def generate_default_profile( |
85 | 102 | render_gif: bool = False, |
86 | 103 | debug: bool = False, |
87 | 104 | reraise: bool = False, |
| 105 | + square: bool = False, |
88 | 106 | **kwargs, |
89 | 107 | ) -> t.Tuple[bytes, bool]: |
| 108 | + """ |
| 109 | + Generate a full profile image with customizable parameters. |
| 110 | + If the avatar is animated and not the background, the avatar will be rendered as a gif. |
| 111 | + If the background is animated and not the avatar, the background will be rendered as a gif. |
| 112 | + If both are animated, the avatar will be rendered as a gif and the background will be rendered as a static image. |
| 113 | + To optimize performance, the profile will be generated in 3 layers, the background, the avatar, and the stats. |
| 114 | + The stats layer will be generated as a separate image and then pasted onto the background. |
| 115 | +
|
| 116 | + Args: |
| 117 | + background (t.Optional[bytes], optional): The background image as bytes. Defaults to None. |
| 118 | + avatar (t.Optional[bytes], optional): The avatar image as bytes. Defaults to None. |
| 119 | + username (t.Optional[str], optional): The username. Defaults to "Spartan117". |
| 120 | + status (t.Optional[str], optional): The status. Defaults to "online". |
| 121 | + level (t.Optional[int], optional): The level. Defaults to 1. |
| 122 | + messages (t.Optional[int], optional): The number of messages. Defaults to 0. |
| 123 | + voicetime (t.Optional[int], optional): The voicetime. Defaults to 3600. |
| 124 | + stars (t.Optional[int], optional): The number of stars. Defaults to 0. |
| 125 | + prestige (t.Optional[int], optional): The prestige level. Defaults to 0. |
| 126 | + prestige_emoji (t.Optional[bytes], optional): The prestige emoji as bytes. Defaults to None. |
| 127 | + balance (t.Optional[int], optional): The balance. Defaults to 0. |
| 128 | + currency_name (t.Optional[str], optional): The name of the currency. Defaults to "Credits". |
| 129 | + previous_xp (t.Optional[int], optional): The previous XP. Defaults to 0. |
| 130 | + current_xp (t.Optional[int], optional): The current XP. Defaults to 0. |
| 131 | + next_xp (t.Optional[int], optional): The next XP. Defaults to 0. |
| 132 | + position (t.Optional[int], optional): The position. Defaults to 0. |
| 133 | + role_icon (t.Optional[bytes, str], optional): The role icon as bytes or url. Defaults to None. |
| 134 | + blur (t.Optional[bool], optional): Whether to blur the box behind the stats. Defaults to False. |
| 135 | + user_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the user. Defaults to None. |
| 136 | + base_color (t.Optional[t.Tuple[int, int, int]], optional): The base color. Defaults to None. |
| 137 | + stat_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the stats. Defaults to None. |
| 138 | + level_bar_color (t.Optional[t.Tuple[int, int, int]], optional): The color for the level bar. Defaults to None. |
| 139 | + font_path (t.Optional[t.Union[str, Path], optional): The path to the font file. Defaults to None. |
| 140 | + render_gif (t.Optional[bool], optional): Whether to render as gif if profile or background is one. Defaults to False. |
| 141 | + debug (t.Optional[bool], optional): Whether to raise any errors rather than suppressing. Defaults to False. |
| 142 | + reraise (t.Optional[bool], optional): Whether to raise any errors rather than suppressing. Defaults to False. |
| 143 | + square (t.Optional[bool], optional): Whether to render the profile as a square. Defaults to False. |
| 144 | + **kwargs: Additional keyword arguments. |
| 145 | +
|
| 146 | + Returns: |
| 147 | + t.Tuple[bytes, bool]: The generated full profile image as bytes, and whether the image is animated. |
| 148 | + """ |
90 | 149 | user_color = user_color or base_color |
91 | 150 | stat_color = stat_color or base_color |
92 | 151 | level_bar_color = level_bar_color or base_color |
@@ -130,30 +189,53 @@ def generate_default_profile( |
130 | 189 | bg_animated = getattr(card, "is_animated", False) |
131 | 190 | log.debug(f"PFP animated: {pfp_animated}, BG animated: {bg_animated}") |
132 | 191 |
|
133 | | - # Ensure the card is the correct size and aspect ratio |
134 | | - desired_card_size = (1050, 450) |
135 | | - # aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size) |
136 | 192 | # Setup |
137 | 193 | default_fill = (0, 0, 0) # Default fill color for text |
138 | 194 | stroke_width = 2 # Width of the stroke around text |
139 | | - name_y = 35 # Upper bound of username placement |
140 | | - stats_y = 160 # Upper bound of stats texts |
141 | | - blur_edge = 450 # Left bound of blur edge |
142 | | - bar_width = 550 # Length of level bar |
143 | | - bar_height = 40 # Height of level bar |
144 | | - bar_start = 475 # Left bound of level bar |
145 | | - bar_top = 380 # Top bound of level bar |
146 | | - stat_bottom = bar_top - 10 # Bottom bound of all stats |
147 | | - stat_start = bar_start + 10 # Left bound of all stats |
148 | | - stat_split = stat_start + 210 # Split between left and right stats |
149 | | - stat_end = 990 # Right bound of all stats |
150 | | - stat_offset = 45 # Offset between stats |
151 | | - circle_x = 60 # Left bound of profile circle |
152 | | - circle_y = 60 # Top bound of profile circle |
153 | | - star_text_x = 910 # Left bound of star text |
154 | | - star_text_y = 35 # Top bound of star text |
155 | | - star_icon_x = 850 # Left bound of star icon |
156 | | - star_icon_y = 35 # Top bound of star icon |
| 195 | + |
| 196 | + if square: |
| 197 | + desired_card_size = (450, 450) |
| 198 | + # aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size) |
| 199 | + name_y = 35 # Upper bound of username placement |
| 200 | + stats_y = 160 # Upper bound of stats texts |
| 201 | + blur_edge = 450 # Left bound of blur edge |
| 202 | + bar_width = 550 # Length of level bar |
| 203 | + bar_height = 40 # Height of level bar |
| 204 | + bar_start = 475 # Left bound of level bar |
| 205 | + bar_top = 380 # Top bound of level bar |
| 206 | + stat_bottom = bar_top - 10 # Bottom bound of all stats |
| 207 | + stat_start = bar_start + 10 # Left bound of all stats |
| 208 | + stat_split = stat_start + 210 # Split between left and right stats |
| 209 | + stat_end = 990 # Right bound of all stats |
| 210 | + stat_offset = 45 # Offset between stats |
| 211 | + circle_x = 60 # Left bound of profile circle |
| 212 | + circle_y = 60 # Top bound of profile circle |
| 213 | + star_text_x = 910 # Left bound of star text |
| 214 | + star_text_y = 35 # Top bound of star text |
| 215 | + star_icon_x = 850 # Left bound of star icon |
| 216 | + star_icon_y = 35 # Top bound of star icon |
| 217 | + else: |
| 218 | + # Ensure the card is the correct size and aspect ratio |
| 219 | + desired_card_size = (1050, 450) |
| 220 | + # aspect_ratio = imgtools.calc_aspect_ratio(*desired_card_size) |
| 221 | + name_y = 35 # Upper bound of username placement |
| 222 | + stats_y = 160 # Upper bound of stats texts |
| 223 | + blur_edge = 450 # Left bound of blur edge |
| 224 | + bar_width = 550 # Length of level bar |
| 225 | + bar_height = 40 # Height of level bar |
| 226 | + bar_start = 475 # Left bound of level bar |
| 227 | + bar_top = 380 # Top bound of level bar |
| 228 | + stat_bottom = bar_top - 10 # Bottom bound of all stats |
| 229 | + stat_start = bar_start + 10 # Left bound of all stats |
| 230 | + stat_split = stat_start + 210 # Split between left and right stats |
| 231 | + stat_end = 990 # Right bound of all stats |
| 232 | + stat_offset = 45 # Offset between stats |
| 233 | + circle_x = 60 # Left bound of profile circle |
| 234 | + circle_y = 60 # Top bound of profile circle |
| 235 | + star_text_x = 910 # Left bound of star text |
| 236 | + star_text_y = 35 # Top bound of star text |
| 237 | + star_icon_x = 850 # Left bound of star icon |
| 238 | + star_icon_y = 35 # Top bound of star icon |
157 | 239 |
|
158 | 240 | # Establish layer for all text and accents |
159 | 241 | stats = Image.new("RGBA", desired_card_size, (0, 0, 0, 0)) |
|
0 commit comments