iconipy
Say goodbye to the tedious hassle of graphic programs! Now you can create stunning icons directly from your Python code. With the look and feel defined right in the code, adjustments are a breeze. Plus, the included icon sets can easily be expanded with your own sets based on font files.
Installation
pip install iconipy
Usage
First you initialize an "IconFactory" with an icon set and look-and-feel settings like this:
from iconipy import IconFactory
create_button_icon = IconFactory(
icon_set = 'lucide',
icon_size = 64,
font_size = 38,
font_color = (0, 0, 0, 255), # black solid
outline_color = 'dimgrey',
outline_width = 6,
background_color = 'silver',
background_radius = 10
)
Then you create your icons by just passing a name to a function call. Iconipy uses a codepoints dictionary to 'translate' names into characters/icons:
icon_home = create_button_icon.asPil('house')
icon_reload = create_button_icon.asPil('refresh-cw')
icon_files = create_button_icon.asPil('files')
icon_exit_app = create_button_icon.asPil('log-out')
Depending on your GUI toolkit's whims, you can create PIL Image Objects, ByteIO Objects, Byte-Strings, Raw Pixel Lists, TkPhotoImage Objects, QImage Objects, save to file, and more.
You need to preview an icon? Here we go:
create_button_icon.show('house')
The icon sets you can choose from include: lucide, boxicons, ligature_symbols, lineicons, material_icons_regular, material_icons_round_regular, material_icons_sharp_regular, material_icons_outlined_regular, microns and typicons.
Feeling adventurous? Dump all the icons to your hard drive and explore:
create_button_icon.saveAll(<path to target directory>)
You need a hand? Check what the icon set has to offer:
print(create_button_icon.search('hand'))
Just want a list with all icon names? No problem:
print(create_button_icon.icon_names)
More info
Visit https://github.com/digidigital/iconipy or https://iconipy.digidigital.de for sample code for the most popular GUI toolkits and detailed documentation.
Iconify vs. iconipy
Iconify is totally unrelated to the iconipy project. Iconify is more mature and powerful, but the focus is on SVG files rather than bitmaps. You can explore it further on their website: https://iconify.design
PyInstaller issues
As of 0.4.0, iconipy has a pyinstaller hook that fixes a previous issue where the assets folder was not added to the frozen application. You no longer need to specify a hidden import or customize the spec file.
iconipy API 0.4.0
1#!/usr/bin/env python3 2 3################################################## 4# Copyright (c) 2024 Björn Seipel, digidigital # 5# This program is released under the MIT license.# 6# Details can be found in the LICENSE file or at:# 7# https://opensource.org/licenses/MIT # 8################################################## 9 10"""Say goodbye to the tedious hassle of graphic programs! Now you can create stunning icons directly 11from your Python code. With the look and feel defined right in the code, adjustments are a breeze. 12Plus, the included icon sets can easily be expanded with your own sets based on font files. 13 14**Installation** 15 16 pip install iconipy 17 18**Usage** 19 20First you initialize an "IconFactory" with an icon set and look-and-feel settings like this: 21 22 from iconipy import IconFactory 23 24 create_button_icon = IconFactory( 25 icon_set = 'lucide', 26 icon_size = 64, 27 font_size = 38, 28 font_color = (0, 0, 0, 255), # black solid 29 outline_color = 'dimgrey', 30 outline_width = 6, 31 background_color = 'silver', 32 background_radius = 10 33 ) 34 35Then you create your icons by just passing a name to a function call. Iconipy uses a codepoints dictionary to 'translate' names into characters/icons: 36 37 icon_home = create_button_icon.asPil('house') 38 icon_reload = create_button_icon.asPil('refresh-cw') 39 icon_files = create_button_icon.asPil('files') 40 icon_exit_app = create_button_icon.asPil('log-out') 41 42Depending on your GUI toolkit's whims, you can create PIL Image Objects, ByteIO Objects, Byte-Strings, Raw Pixel Lists, TkPhotoImage Objects, QImage Objects, save to file, and more. 43 44You need to preview an icon? Here we go: 45 46 create_button_icon.show('house') 47 48The icon sets you can choose from include: lucide, boxicons, ligature_symbols, lineicons, material_icons_regular, material_icons_round_regular, material_icons_sharp_regular, material_icons_outlined_regular, microns and typicons. 49 50Feeling adventurous? Dump all the icons to your hard drive and explore: 51 52 create_button_icon.saveAll(<path to target directory>) 53 54You need a hand? Check what the icon set has to offer: 55 56 print(create_button_icon.search('hand')) 57 58Just want a list with all icon names? No problem: 59 60 print(create_button_icon.icon_names) 61 62**More info** 63 64Visit https://github.com/digidigital/iconipy or https://iconipy.digidigital.de for sample code for the most popular GUI toolkits and detailed documentation. 65 66**Iconify vs. iconipy** 67 68Iconify is totally unrelated to the iconipy project. Iconify is more mature and powerful, but the focus is on SVG files rather than bitmaps. You can explore it further on their website: https://iconify.design 69 70 71**PyInstaller issues** 72 73As of 0.4.0, iconipy has a pyinstaller hook that fixes a previous issue where the assets folder was not added to the frozen application. You no longer need to specify a hidden import or customize the spec file. 74 75# iconipy API 0.4.0""" 76 77import os 78import io 79import re 80import sys 81import json 82import uuid 83from PIL import Image, ImageTk, ImageQt, ImageDraw, ImageFont, ImageOps 84from tempfile import TemporaryDirectory 85from typing import Union, Tuple 86 87_ColorAttributeType = Union[Tuple, str] 88_SizeAttributeType = Union[Tuple, int] 89 90_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 91_ASSET_PATH = os.path.join(_SCRIPT_DIR, "assets") 92_SCRIPT_VERSION = "0.4.0" 93 94_lucide_cfg = { 95 "FONT_FILE": os.path.join(_ASSET_PATH, "lucide", "lucide.ttf"), 96 "METADATA_FILE": os.path.join(_ASSET_PATH, "lucide", "info.json"), 97 "VERSION_FILE": os.path.join(_ASSET_PATH, "lucide", "version.txt"), 98 "LICENSE_FILE": os.path.join(_ASSET_PATH, "lucide", "LICENSE.txt"), 99} 100 101_ligature_symbols_cfg = { 102 "FONT_FILE": os.path.join(_ASSET_PATH, "ligature_symbols", "LigatureSymbols-2.11.ttf"), 103 "METADATA_FILE": os.path.join(_ASSET_PATH, "ligature_symbols", "index.html"), 104 "VERSION_FILE": os.path.join(_ASSET_PATH, "ligature_symbols", "version.txt"), 105 "LICENSE_FILE": os.path.join(_ASSET_PATH, "ligature_symbols", "LICENSE.txt"), 106} 107 108_microns_cfg = { 109 "FONT_FILE": os.path.join(_ASSET_PATH, "microns", "microns.ttf"), 110 "METADATA_FILE": os.path.join(_ASSET_PATH, "microns", "microns.css"), 111 "VERSION_FILE": os.path.join(_ASSET_PATH, "microns", "version.txt"), 112 "LICENSE_FILE": os.path.join(_ASSET_PATH, "microns", "LICENCE.md"), 113} 114 115_typicons_cfg = { 116 "FONT_FILE": os.path.join(_ASSET_PATH, "typicons", "typicons.ttf"), 117 "METADATA_FILE": os.path.join(_ASSET_PATH, "typicons", "typicons.css"), 118 "VERSION_FILE": os.path.join(_ASSET_PATH, "typicons", "version.txt"), 119 "LICENSE_FILE": os.path.join(_ASSET_PATH, "typicons", "LICENCE.md"), 120} 121 122_boxicons_cfg = { 123 "FONT_FILE": os.path.join(_ASSET_PATH, "boxicons", "boxicons.ttf"), 124 "METADATA_FILE": os.path.join(_ASSET_PATH, "boxicons", "boxicons.css"), 125 "VERSION_FILE": os.path.join(_ASSET_PATH, "boxicons", "version.txt"), 126 "LICENSE_FILE": os.path.join(_ASSET_PATH, "boxicons", "LICENSE.txt"), 127} 128 129_lineicons_cfg = { 130 "FONT_FILE": os.path.join(_ASSET_PATH, "lineicons", "lineicons.ttf"), 131 "METADATA_FILE": os.path.join(_ASSET_PATH, "lineicons", "unicodesMap.json"), 132 "VERSION_FILE": os.path.join(_ASSET_PATH, "lineicons", "version.txt"), 133 "LICENSE_FILE": os.path.join(_ASSET_PATH, "lineicons", "LICENSE.md"), 134} 135 136_material_icons_regular_cfg = { 137 "FONT_FILE": os.path.join( 138 _ASSET_PATH, 139 "material_icons_regular", 140 "MaterialIcons-Regular.ttf" 141 ), 142 "METADATA_FILE": os.path.join( 143 _ASSET_PATH, 144 "material_icons_regular", 145 "MaterialIcons-Regular.codepoints" 146 ), 147 "VERSION_FILE": os.path.join( 148 _ASSET_PATH, 149 "material_icons_regular", 150 "version.txt" 151 ), 152 "LICENSE_FILE": os.path.join( 153 _ASSET_PATH, 154 "material_icons_regular", 155 "LICENSE.txt" 156 ), 157} 158 159_material_icons_round_regular_cfg = { 160 "FONT_FILE": os.path.join( 161 _ASSET_PATH, 162 "material_icons_round_regular", 163 "MaterialIconsRound-Regular.otf" 164 ), 165 "METADATA_FILE": os.path.join( 166 _ASSET_PATH, 167 "material_icons_round_regular", 168 "MaterialIconsRound-Regular.codepoints" 169 ), 170 "VERSION_FILE": os.path.join( 171 _ASSET_PATH, 172 "material_icons_round_regular", 173 "version.txt" 174 ), 175 "LICENSE_FILE": os.path.join( 176 _ASSET_PATH, 177 "material_icons_round_regular", 178 "LICENSE.txt" 179 ), 180} 181 182_material_icons_sharp_regular_cfg = { 183 "FONT_FILE": os.path.join( 184 _ASSET_PATH, 185 "material_icons_sharp_regular", 186 "MaterialIconsSharp-Regular.otf" 187 ), 188 "METADATA_FILE": os.path.join( 189 _ASSET_PATH, 190 "material_icons_sharp_regular", 191 "MaterialIconsSharp-Regular.codepoints" 192 ), 193 "VERSION_FILE": os.path.join( 194 _ASSET_PATH, 195 "material_icons_sharp_regular", 196 "version.txt" 197 ), 198 "LICENSE_FILE": os.path.join( 199 _ASSET_PATH, 200 "material_icons_sharp_regular", 201 "LICENSE.txt" 202 ), 203} 204 205_material_icons_outlined_regular_cfg = { 206 "FONT_FILE": os.path.join( 207 _ASSET_PATH, 208 "material_icons_outlined_regular", 209 "MaterialIconsOutlined-Regular.otf" 210 ), 211 "METADATA_FILE": os.path.join( 212 _ASSET_PATH, 213 "material_icons_outlined_regular", 214 "MaterialIconsOutlined-Regular.codepoints" 215 ), 216 "VERSION_FILE": os.path.join( 217 _ASSET_PATH, 218 "material_icons_outlined_regular", 219 "version.txt" 220 ), 221 "LICENSE_FILE": os.path.join( 222 _ASSET_PATH, 223 "material_icons_outlined_regular", 224 "LICENSE.txt" 225 ), 226} 227 228_ICON_SETS = { 229 "lucide": _lucide_cfg, 230 "ligature_symbols": _ligature_symbols_cfg, 231 "microns": _microns_cfg, 232 "typicons": _typicons_cfg, 233 "boxicons": _boxicons_cfg, 234 "lineicons": _lineicons_cfg, 235 "material_icons_regular": _material_icons_regular_cfg, 236 "material_icons_round_regular": _material_icons_round_regular_cfg, 237 "material_icons_sharp_regular": _material_icons_sharp_regular_cfg, 238 "material_icons_outlined_regular": _material_icons_outlined_regular_cfg, 239} 240 241class IconFactory: 242 """Create an IconFactory for one of the icon sets included with iconipy. All icons created by this 243 IconFactory will share the same settings, allowing you to change the style for all icons upon 244 initialization. 245 246 icon_set (str): The name of the icon set that will be used to create the icon. 247 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 248 font_size (int): The size of the font. Default is icon_size 249 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 250 outline_width (int): The width of the outline. 0 does not draw an outline 251 outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string 252 background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string 253 background_radius (int): The radius of the background corners. 254 """ 255 256 _all_codepoints = {} 257 258 def __init__( 259 self, 260 icon_set: str = "lucide", 261 icon_size: _SizeAttributeType = 64, 262 font_size: int = None, 263 font_color: _ColorAttributeType = "black", 264 outline_width: int = 0, 265 outline_color: _ColorAttributeType = "black", 266 background_color: _ColorAttributeType = None, 267 background_radius: int = 0, 268 ) -> None: 269 if not icon_set in _ICON_SETS.keys(): 270 raise ValueError(f'Unknown icon set "{icon_set}"') 271 272 self.icon_set_name = icon_set 273 '''Stores the name of the icon set that is used to create the icons''' 274 275 self.icon_set_version = self._get_icon_set_version( 276 _ICON_SETS[icon_set]["VERSION_FILE"] 277 ) 278 '''Stores the version string for the icon set''' 279 280 try: 281 self._codepoints = IconFactory._all_codepoints[icon_set] 282 except KeyError: 283 IconFactory._all_codepoints = self._read_codepoints() 284 self._codepoints = IconFactory._all_codepoints[icon_set] 285 286 self.icon_names = list(self._codepoints.keys()) 287 '''A list of all icon names for the selected icon set. When the documentation states that *"name" must be a valid key for the codepoints dictionary*, it means the name you enter must be included in this list.''' 288 289 self.license = self._get_license_text( 290 _ICON_SETS[icon_set]["LICENSE_FILE"] 291 ) 292 '''The icon set's license''' 293 font_size = self._check_font_vs_icon_size(font_size, icon_size) 294 295 self.icon_sets_available = list(_ICON_SETS.keys()) 296 '''A list containing all icon sets that are installed''' 297 298 self._drawing_kwargs = { 299 "font_path": _ICON_SETS[icon_set]["FONT_FILE"], 300 "icon_size": icon_size, 301 "font_size": font_size, 302 "font_color": font_color, 303 "icon_outline_width": outline_width, 304 "icon_outline_color": outline_color, 305 "icon_background_color": background_color, 306 "icon_background_radius": background_radius, 307 } 308 309 self._temp_dir = TemporaryDirectory() 310 311 def changeIconSet(self, icon_set: str) -> list: 312 '''Change to a different icon set and retrieve a list containing the icon names 313 of the new set. Available icon sets include: lucide, boxicons, lineicons, material_icons_regular, 314 material_icons_round_regular, material_icons_sharp_regular, and material_icons_outlined_regular. 315 316 icon_set (str): The name of the icon set that will be used to create the icons. 317 ''' 318 if not icon_set in _ICON_SETS.keys(): 319 raise ValueError(f'Unknown icon set "{icon_set}"') 320 self.icon_set_name=icon_set 321 self._drawing_kwargs['font_path']=_ICON_SETS[icon_set]["FONT_FILE"] 322 self._codepoints = IconFactory._all_codepoints[icon_set] 323 self.icon_names = list(self._codepoints.keys()) 324 self.license = self._get_license_text( 325 _ICON_SETS[icon_set]["LICENSE_FILE"] 326 ) 327 self.icon_set_version = self._get_icon_set_version( 328 _ICON_SETS[icon_set]["VERSION_FILE"] 329 ) 330 return self.icon_names 331 332 def updateCfg (self, 333 icon_size: _SizeAttributeType = None, 334 font_size: int = None, 335 font_color: _ColorAttributeType = None, 336 outline_width: int = None, 337 outline_color: _ColorAttributeType = None, 338 background_color: _ColorAttributeType = None, 339 background_radius: int = None, 340 ) -> dict: 341 '''Modify one or more parameters of the IconFactory object and retrieve a dictionary containing 342 the updated configuration. Typically, distinct IconFactories are created for different icon 343 styles, but there may be scenarios where reusing an existing object is desirable. 344 345 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 346 font_size (int): The size of the font. Default is icon_size 347 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 348 outline_width (int): The width of the outline. 0 does not draw an outline 349 outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string 350 background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string 351 background_radius (int): The radius of the background corners. 352 ''' 353 if not icon_size == None: 354 if isinstance(icon_size, int): 355 icon_size = icon_size if icon_size>0 else 1 356 elif isinstance(icon_size, tuple): 357 icon_size = (icon_size[0] if icon_size[0]>0 else 1, icon_size[1] if icon_size[1]>0 else 1) 358 self._drawing_kwargs['icon_size']=icon_size 359 if not font_size ==None: 360 self._drawing_kwargs['font_size']=self._check_font_vs_icon_size(font_size, icon_size) 361 if not font_color == None: 362 self._drawing_kwargs['font_color']=font_color 363 if not outline_width == None: 364 self._drawing_kwargs['icon_outline_width']=outline_width if outline_width>=0 else 0 365 if not outline_color == None: 366 self._drawing_kwargs['icon_outline_color']=outline_color 367 if not background_color == None: 368 self._drawing_kwargs['icon_background_color']=background_color 369 if not background_radius == None: 370 self._drawing_kwargs['icon_background_radius']=background_radius if background_radius>=0 else 0 371 372 return self._drawing_kwargs 373 374 def _check_font_vs_icon_size(self, font_size, icon_size): 375 if isinstance(icon_size, int): 376 smallest_side = icon_size 377 elif isinstance(icon_size, tuple) and len(icon_size)==2 and isinstance(icon_size[0], int) and isinstance(icon_size[1], int): 378 smallest_side = icon_size[0] if icon_size[0] <= icon_size[1] else icon_size[1] 379 else: 380 raise AttributeError('icon_size must be int or tuple (int,int)') 381 382 if isinstance(font_size, int) and font_size <= smallest_side: 383 return font_size if font_size >= 0 else 0 384 else: 385 return smallest_side if smallest_side > 0 else 1 386 387 def _read_codepoints(self): 388 codepoints = {} 389 for icon_set in _ICON_SETS.keys(): 390 icon_set_codepoints = {} 391 _METADATA_FILE = _ICON_SETS[icon_set]["METADATA_FILE"] 392 393 if icon_set == "lucide": 394 with open(_METADATA_FILE) as json_data: 395 codepoint_data = json.load(json_data) 396 for key in codepoint_data.keys(): 397 icon_set_codepoints[key] = codepoint_data[key]["encodedCode"][1:] 398 399 elif icon_set in ("boxicons", "microns", "typicons"): 400 pattern = re.compile( 401 r'.*\.(?P<codepoint_key>([a-z0-9]*-){1,4}[a-z0-9]*):.*\n.*"\\(?P<codepoint_value>.*)";' 402 ) 403 404 with open(_METADATA_FILE, "r") as boxicons_codepoint_file: 405 raw_content = boxicons_codepoint_file.read() 406 407 # Find all matches in the file content 408 matches = pattern.finditer(raw_content) 409 410 # Populate the dictionary with the matches 411 for match in matches: 412 codepoint_key = match.group("codepoint_key") 413 codepoint_value = match.group("codepoint_value") 414 icon_set_codepoints[codepoint_key] = codepoint_value 415 416 elif icon_set == "ligature_symbols": 417 # Read the HTML content from a file 418 with open(_METADATA_FILE, 'r', encoding='utf-8') as ls_codepoint_file: 419 html_string = ls_codepoint_file.read() 420 421 pattern = re.compile( 422 r'<td class="lsf symbol">(.+?)</td>\s*<td class="ligature">.+?</td>\s*<td class="unicode">\\(.+?)</td>' 423 ) 424 425 # Find matches 426 matches = pattern.findall(html_string) 427 428 # Populate the dictionary with the matches 429 icon_set_codepoints = {match[0]: match[1] for match in matches} 430 431 elif icon_set == "lineicons": 432 with open(_METADATA_FILE) as json_data: 433 codepoint_data = json.load(json_data) 434 for key, value in codepoint_data.items(): 435 icon_set_codepoints[key] = hex(value)[2:] 436 437 else: 438 # Material icons 439 with open(_METADATA_FILE) as material_icons_codepoint_file: 440 for line in material_icons_codepoint_file: 441 codepoint_key, codepoint_value = line.strip().split() 442 icon_set_codepoints[codepoint_key] = codepoint_value 443 444 codepoints[icon_set] = icon_set_codepoints 445 446 return codepoints 447 448 def _get_license_text(self, license_file): 449 with open(license_file, "r") as file_handle: 450 return file_handle.read() 451 452 def _get_icon_set_version(self, version_file): 453 with open(version_file, "r") as file_handle: 454 return file_handle.readline().rstrip() 455 456 def _draw_character( 457 self, 458 character, 459 font_path, 460 font_size=32, 461 font_color="grey", 462 icon_size=32, 463 icon_background_color=None, 464 icon_outline_width=0, 465 icon_background_radius=0, 466 icon_outline_color="dimgrey", 467 ): 468 469 # Create image 470 if isinstance (icon_size, int): 471 width = height = icon_size 472 elif isinstance (icon_size, tuple) and isinstance (icon_size[0], int) and isinstance (icon_size[1], int): 473 width = icon_size[0] 474 height = icon_size[1] 475 else: 476 raise AttributeError ('icon_size must be of type int or tuple (int, int)') 477 478 # Add a background 479 if icon_background_color or icon_outline_width: 480 image = self._image_round_background( 481 size = (width, height), 482 fill = icon_background_color, 483 outline = icon_outline_color, 484 outline_width = icon_outline_width, 485 outline_radius = icon_background_radius, 486 ) 487 else: 488 image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) 489 490 if font_size > 0: 491 # Load font 492 font = ImageFont.truetype(font_path, font_size) 493 494 # Draw character 495 draw = ImageDraw.Draw(image) 496 497 x = width // 2 498 y = height // 2 499 500 draw.text((x, y), character, font=font, anchor="mm", fill=font_color) 501 return image 502 503 def _image_round_background( 504 self, 505 size = (64,64), 506 fill = "silver", 507 outline = "grey", 508 outline_width = 7, 509 outline_radius = 10, 510 factor = 3, 511 ): 512 """Create and return a background image with rounded corners. Set the outline radius to size/2 to achieve a circular background.""" 513 width = size[0] 514 height = size[1] 515 im = Image.new("RGBA", (factor * width, factor * height), (0, 0, 0, 0)) 516 draw = ImageDraw.Draw(im, "RGBA") 517 draw.rounded_rectangle( 518 (0, 0, (factor * width)-1, (factor * height)-1), 519 radius=factor * outline_radius, 520 outline=outline, 521 fill=fill, 522 width=factor * outline_width, 523 ) 524 im = im.resize((width, height), Image.LANCZOS) 525 return im 526 527 def search(self, search_name: str) -> list: 528 """Search for an icon name. Returns a list of icon names containing the search_name""" 529 return [icon_name for icon_name in self.icon_names if search_name.lower() in icon_name.lower()] 530 531 def asPil(self, name: str): 532 """Create image as PIL Image Object, "name" must be a valid key for the codepoints dictionary""" 533 if not name in self._codepoints: 534 raise ValueError( 535 f'Icon with name "{name}" not available. Icon Set: {self.icon_set_name}, Version: {self.icon_set_version}' 536 ) 537 538 character = chr(int(self._codepoints[name], 16)) 539 return self._draw_character(character, **self._drawing_kwargs) 540 541 def asTkPhotoImage(self, name: str): 542 """Create image as tkinter PhotoImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary""" 543 return ImageTk.PhotoImage(self.asPil(name)) 544 545 def asTkBitmapImage(self, name: str): 546 """Create image as *monochrome* (two-color) tkinter BitmapImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary""" 547 mode_one_img = self.asPil(name).convert("1") 548 inverted_img = ImageOps.invert(mode_one_img) 549 return ImageTk.BitmapImage(inverted_img) 550 551 def asBytes(self, name: str, image_format: str="PNG"): 552 """Returns image data as bytestring, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF""" 553 with io.BytesIO() as output: 554 self.asPil(name).save(output, format=image_format) 555 return output.getvalue() 556 557 def asBytesIo(self, name: str, image_format: str="PNG"): 558 """Returns image data as BytesIO object, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF""" 559 output = io.BytesIO() 560 self.asPil(name).save(output, format=image_format) 561 output.seek(0) 562 return output 563 564 def asRawList(self, name: str, type: str="RGB"): 565 """Returns the pixel data of the image as a list. "name" must be a valid key for the codepoints dictionary, type="RGB" contains values 0-255, type="FLOAT" contains values 0-1""" 566 567 def _calc_pixel_value(value, type): 568 if type == "FLOAT": 569 return value / 255.0 570 return value # 'RGB' or any other type 571 572 icon = self.asPil(name) 573 pixel_data = [] 574 # Process image to list 575 # numpy etc. are WAY faster but introduce new dependencies 576 for i in range(0, icon.height): 577 for j in range(0, icon.width): 578 pixel = icon.getpixel((j, i)) 579 pixel_data.append(_calc_pixel_value(pixel[0], type)) 580 pixel_data.append(_calc_pixel_value(pixel[1], type)) 581 pixel_data.append(_calc_pixel_value(pixel[2], type)) 582 pixel_data.append(_calc_pixel_value(pixel[3], type)) 583 return pixel_data 584 585 def asQImage(self, name: str): 586 """Create image as QImage Object, "name" must be a valid key for the codepoints dictionary""" 587 return ImageQt.ImageQt(self.asPil(name)) 588 589 def asQPixmap(self, name: str): 590 """Create image as QPixmap Object, "name" must be a valid key for the codepoints dictionary""" 591 return ImageQt.toqpixmap(self.asPil(name)) 592 593 def asTempFile(self, name: str, extension: str="png"): 594 '''Returns a path to a temporary image file. If your framework only accepts file paths, you can use this function. The image format is determined by the file extension (Default is "png") and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary.''' 595 filepath = os.path.join(self._temp_dir.name, f'{str(uuid.uuid4())}.{extension.lower()}') 596 self.save(name, filepath) 597 return filepath 598 599 def save(self, name: str, save_as: str): 600 """Saves the icon to file "save_as", the image format is determined by the file extension and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary""" 601 kwargs={} 602 if save_as.lower().endswith('.ico'): 603 kwargs['sizes'] = [self._drawing_kwargs['icon_size']] 604 self.asPil(name).save(save_as, **kwargs) 605 606 def saveAll(self, save_to_dir: str, extension: str="png"): 607 '''Saves all icons in the icon set to path "save_to_dir", the image format is determined by the "extension" and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported.''' 608 for name in self._codepoints.keys(): 609 self.save(name, os.path.join(save_to_dir, f"{name}.{extension.lower()}")) 610 611 def show(self, name: str): 612 """Show the icon in an external viewer using the PIL Image.show() method. "name" must be a valid key for the codepoints dictionary""" 613 self.asPil(name).show() 614 615 616class CustomIconFactory(IconFactory): 617 """Create an IconFactory for a custom icon set by providing a font path 618 (supported by the FreeType library, e.g., TrueType, OpenType) and a 619 dictionary of codepoints. The dictionary keys should be the icon names 620 ('microphone'), and the values should be the corresponding 621 hexadecimal codepoints ('E02A'). 622 623 icon_set (str): The name of the icon set that will be used to create the icon. 624 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 625 font_size (int): The size of the font. Default is icon_size 626 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 627 outline_width (int): The width of the outline. 0 does not draw an outline 628 outline_color (str, tuple): The color of the outline. Name, RGBA-Tuple or hex string 629 background_color (str, tuple): The background color. Name, RGBA-Tuple or hex string 630 background_radius (int): The radius of the background corners. 631 font_path (str): The path to the custom icon set font file. 632 codepoints (dict): A dictionary of icon names and codepoints. 633 version (str): The version of the icon set. 634 """ 635 636 def __init__( 637 self, 638 icon_set: str = "custom", 639 icon_size: _SizeAttributeType = 64, 640 font_size: int = None, 641 font_color: _ColorAttributeType = "black", 642 outline_width: int = 0, 643 outline_color: _ColorAttributeType = "black", 644 background_color: _ColorAttributeType = None, 645 background_radius: int = 0, 646 font_path: str = None, 647 codepoints: dict = None, 648 version: str = "0.1", 649 ) -> None: 650 if not font_path or not codepoints: 651 raise ValueError( 652 f'You need to supply a font path and codepoint dictionary"' 653 ) 654 655 self.icon_set_name = icon_set 656 '''Stores the name of the icon set''' 657 658 self.icon_set_version = version 659 '''Stores the version string for the icon set''' 660 661 self._codepoints = codepoints 662 663 self.icon_names = list(self._codepoints.keys()) 664 '''A list of all icon names for the selected icon set. When the documentation states that *"name" must be a valid key for the codepoints dictionary*, it means the name you enter must be included in this list.''' 665 666 self.license = 'Unknown License' 667 '''For custom icon sets the default value is "Unknown License"''' 668 669 font_size = self._check_font_vs_icon_size(font_size, icon_size) 670 671 self._drawing_kwargs = { 672 "font_path": font_path, 673 "font_size": font_size, 674 "font_color": font_color, 675 "icon_size": icon_size, 676 "icon_background_color": background_color, 677 "icon_outline_width": outline_width, 678 "icon_background_radius": background_radius, 679 "icon_outline_color": outline_color, 680 } 681 682 def changeIconSet(self, icon_set: str): 683 '''Not implemented for CustomIconFactory''' 684 raise NotImplementedError('changeIconSet is not implemented for CustomIconFactory') 685 686 687if __name__ == "__main__": 688 print(f"iconipy {_SCRIPT_VERSION}") 689 print("--------------------------------------") 690 for set in _ICON_SETS.keys(): 691 version = IconFactory._get_icon_set_version(None, _ICON_SETS[set]["VERSION_FILE"]) 692 print(f"{set} {version}")
242class IconFactory: 243 """Create an IconFactory for one of the icon sets included with iconipy. All icons created by this 244 IconFactory will share the same settings, allowing you to change the style for all icons upon 245 initialization. 246 247 icon_set (str): The name of the icon set that will be used to create the icon. 248 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 249 font_size (int): The size of the font. Default is icon_size 250 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 251 outline_width (int): The width of the outline. 0 does not draw an outline 252 outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string 253 background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string 254 background_radius (int): The radius of the background corners. 255 """ 256 257 _all_codepoints = {} 258 259 def __init__( 260 self, 261 icon_set: str = "lucide", 262 icon_size: _SizeAttributeType = 64, 263 font_size: int = None, 264 font_color: _ColorAttributeType = "black", 265 outline_width: int = 0, 266 outline_color: _ColorAttributeType = "black", 267 background_color: _ColorAttributeType = None, 268 background_radius: int = 0, 269 ) -> None: 270 if not icon_set in _ICON_SETS.keys(): 271 raise ValueError(f'Unknown icon set "{icon_set}"') 272 273 self.icon_set_name = icon_set 274 '''Stores the name of the icon set that is used to create the icons''' 275 276 self.icon_set_version = self._get_icon_set_version( 277 _ICON_SETS[icon_set]["VERSION_FILE"] 278 ) 279 '''Stores the version string for the icon set''' 280 281 try: 282 self._codepoints = IconFactory._all_codepoints[icon_set] 283 except KeyError: 284 IconFactory._all_codepoints = self._read_codepoints() 285 self._codepoints = IconFactory._all_codepoints[icon_set] 286 287 self.icon_names = list(self._codepoints.keys()) 288 '''A list of all icon names for the selected icon set. When the documentation states that *"name" must be a valid key for the codepoints dictionary*, it means the name you enter must be included in this list.''' 289 290 self.license = self._get_license_text( 291 _ICON_SETS[icon_set]["LICENSE_FILE"] 292 ) 293 '''The icon set's license''' 294 font_size = self._check_font_vs_icon_size(font_size, icon_size) 295 296 self.icon_sets_available = list(_ICON_SETS.keys()) 297 '''A list containing all icon sets that are installed''' 298 299 self._drawing_kwargs = { 300 "font_path": _ICON_SETS[icon_set]["FONT_FILE"], 301 "icon_size": icon_size, 302 "font_size": font_size, 303 "font_color": font_color, 304 "icon_outline_width": outline_width, 305 "icon_outline_color": outline_color, 306 "icon_background_color": background_color, 307 "icon_background_radius": background_radius, 308 } 309 310 self._temp_dir = TemporaryDirectory() 311 312 def changeIconSet(self, icon_set: str) -> list: 313 '''Change to a different icon set and retrieve a list containing the icon names 314 of the new set. Available icon sets include: lucide, boxicons, lineicons, material_icons_regular, 315 material_icons_round_regular, material_icons_sharp_regular, and material_icons_outlined_regular. 316 317 icon_set (str): The name of the icon set that will be used to create the icons. 318 ''' 319 if not icon_set in _ICON_SETS.keys(): 320 raise ValueError(f'Unknown icon set "{icon_set}"') 321 self.icon_set_name=icon_set 322 self._drawing_kwargs['font_path']=_ICON_SETS[icon_set]["FONT_FILE"] 323 self._codepoints = IconFactory._all_codepoints[icon_set] 324 self.icon_names = list(self._codepoints.keys()) 325 self.license = self._get_license_text( 326 _ICON_SETS[icon_set]["LICENSE_FILE"] 327 ) 328 self.icon_set_version = self._get_icon_set_version( 329 _ICON_SETS[icon_set]["VERSION_FILE"] 330 ) 331 return self.icon_names 332 333 def updateCfg (self, 334 icon_size: _SizeAttributeType = None, 335 font_size: int = None, 336 font_color: _ColorAttributeType = None, 337 outline_width: int = None, 338 outline_color: _ColorAttributeType = None, 339 background_color: _ColorAttributeType = None, 340 background_radius: int = None, 341 ) -> dict: 342 '''Modify one or more parameters of the IconFactory object and retrieve a dictionary containing 343 the updated configuration. Typically, distinct IconFactories are created for different icon 344 styles, but there may be scenarios where reusing an existing object is desirable. 345 346 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 347 font_size (int): The size of the font. Default is icon_size 348 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 349 outline_width (int): The width of the outline. 0 does not draw an outline 350 outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string 351 background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string 352 background_radius (int): The radius of the background corners. 353 ''' 354 if not icon_size == None: 355 if isinstance(icon_size, int): 356 icon_size = icon_size if icon_size>0 else 1 357 elif isinstance(icon_size, tuple): 358 icon_size = (icon_size[0] if icon_size[0]>0 else 1, icon_size[1] if icon_size[1]>0 else 1) 359 self._drawing_kwargs['icon_size']=icon_size 360 if not font_size ==None: 361 self._drawing_kwargs['font_size']=self._check_font_vs_icon_size(font_size, icon_size) 362 if not font_color == None: 363 self._drawing_kwargs['font_color']=font_color 364 if not outline_width == None: 365 self._drawing_kwargs['icon_outline_width']=outline_width if outline_width>=0 else 0 366 if not outline_color == None: 367 self._drawing_kwargs['icon_outline_color']=outline_color 368 if not background_color == None: 369 self._drawing_kwargs['icon_background_color']=background_color 370 if not background_radius == None: 371 self._drawing_kwargs['icon_background_radius']=background_radius if background_radius>=0 else 0 372 373 return self._drawing_kwargs 374 375 def _check_font_vs_icon_size(self, font_size, icon_size): 376 if isinstance(icon_size, int): 377 smallest_side = icon_size 378 elif isinstance(icon_size, tuple) and len(icon_size)==2 and isinstance(icon_size[0], int) and isinstance(icon_size[1], int): 379 smallest_side = icon_size[0] if icon_size[0] <= icon_size[1] else icon_size[1] 380 else: 381 raise AttributeError('icon_size must be int or tuple (int,int)') 382 383 if isinstance(font_size, int) and font_size <= smallest_side: 384 return font_size if font_size >= 0 else 0 385 else: 386 return smallest_side if smallest_side > 0 else 1 387 388 def _read_codepoints(self): 389 codepoints = {} 390 for icon_set in _ICON_SETS.keys(): 391 icon_set_codepoints = {} 392 _METADATA_FILE = _ICON_SETS[icon_set]["METADATA_FILE"] 393 394 if icon_set == "lucide": 395 with open(_METADATA_FILE) as json_data: 396 codepoint_data = json.load(json_data) 397 for key in codepoint_data.keys(): 398 icon_set_codepoints[key] = codepoint_data[key]["encodedCode"][1:] 399 400 elif icon_set in ("boxicons", "microns", "typicons"): 401 pattern = re.compile( 402 r'.*\.(?P<codepoint_key>([a-z0-9]*-){1,4}[a-z0-9]*):.*\n.*"\\(?P<codepoint_value>.*)";' 403 ) 404 405 with open(_METADATA_FILE, "r") as boxicons_codepoint_file: 406 raw_content = boxicons_codepoint_file.read() 407 408 # Find all matches in the file content 409 matches = pattern.finditer(raw_content) 410 411 # Populate the dictionary with the matches 412 for match in matches: 413 codepoint_key = match.group("codepoint_key") 414 codepoint_value = match.group("codepoint_value") 415 icon_set_codepoints[codepoint_key] = codepoint_value 416 417 elif icon_set == "ligature_symbols": 418 # Read the HTML content from a file 419 with open(_METADATA_FILE, 'r', encoding='utf-8') as ls_codepoint_file: 420 html_string = ls_codepoint_file.read() 421 422 pattern = re.compile( 423 r'<td class="lsf symbol">(.+?)</td>\s*<td class="ligature">.+?</td>\s*<td class="unicode">\\(.+?)</td>' 424 ) 425 426 # Find matches 427 matches = pattern.findall(html_string) 428 429 # Populate the dictionary with the matches 430 icon_set_codepoints = {match[0]: match[1] for match in matches} 431 432 elif icon_set == "lineicons": 433 with open(_METADATA_FILE) as json_data: 434 codepoint_data = json.load(json_data) 435 for key, value in codepoint_data.items(): 436 icon_set_codepoints[key] = hex(value)[2:] 437 438 else: 439 # Material icons 440 with open(_METADATA_FILE) as material_icons_codepoint_file: 441 for line in material_icons_codepoint_file: 442 codepoint_key, codepoint_value = line.strip().split() 443 icon_set_codepoints[codepoint_key] = codepoint_value 444 445 codepoints[icon_set] = icon_set_codepoints 446 447 return codepoints 448 449 def _get_license_text(self, license_file): 450 with open(license_file, "r") as file_handle: 451 return file_handle.read() 452 453 def _get_icon_set_version(self, version_file): 454 with open(version_file, "r") as file_handle: 455 return file_handle.readline().rstrip() 456 457 def _draw_character( 458 self, 459 character, 460 font_path, 461 font_size=32, 462 font_color="grey", 463 icon_size=32, 464 icon_background_color=None, 465 icon_outline_width=0, 466 icon_background_radius=0, 467 icon_outline_color="dimgrey", 468 ): 469 470 # Create image 471 if isinstance (icon_size, int): 472 width = height = icon_size 473 elif isinstance (icon_size, tuple) and isinstance (icon_size[0], int) and isinstance (icon_size[1], int): 474 width = icon_size[0] 475 height = icon_size[1] 476 else: 477 raise AttributeError ('icon_size must be of type int or tuple (int, int)') 478 479 # Add a background 480 if icon_background_color or icon_outline_width: 481 image = self._image_round_background( 482 size = (width, height), 483 fill = icon_background_color, 484 outline = icon_outline_color, 485 outline_width = icon_outline_width, 486 outline_radius = icon_background_radius, 487 ) 488 else: 489 image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) 490 491 if font_size > 0: 492 # Load font 493 font = ImageFont.truetype(font_path, font_size) 494 495 # Draw character 496 draw = ImageDraw.Draw(image) 497 498 x = width // 2 499 y = height // 2 500 501 draw.text((x, y), character, font=font, anchor="mm", fill=font_color) 502 return image 503 504 def _image_round_background( 505 self, 506 size = (64,64), 507 fill = "silver", 508 outline = "grey", 509 outline_width = 7, 510 outline_radius = 10, 511 factor = 3, 512 ): 513 """Create and return a background image with rounded corners. Set the outline radius to size/2 to achieve a circular background.""" 514 width = size[0] 515 height = size[1] 516 im = Image.new("RGBA", (factor * width, factor * height), (0, 0, 0, 0)) 517 draw = ImageDraw.Draw(im, "RGBA") 518 draw.rounded_rectangle( 519 (0, 0, (factor * width)-1, (factor * height)-1), 520 radius=factor * outline_radius, 521 outline=outline, 522 fill=fill, 523 width=factor * outline_width, 524 ) 525 im = im.resize((width, height), Image.LANCZOS) 526 return im 527 528 def search(self, search_name: str) -> list: 529 """Search for an icon name. Returns a list of icon names containing the search_name""" 530 return [icon_name for icon_name in self.icon_names if search_name.lower() in icon_name.lower()] 531 532 def asPil(self, name: str): 533 """Create image as PIL Image Object, "name" must be a valid key for the codepoints dictionary""" 534 if not name in self._codepoints: 535 raise ValueError( 536 f'Icon with name "{name}" not available. Icon Set: {self.icon_set_name}, Version: {self.icon_set_version}' 537 ) 538 539 character = chr(int(self._codepoints[name], 16)) 540 return self._draw_character(character, **self._drawing_kwargs) 541 542 def asTkPhotoImage(self, name: str): 543 """Create image as tkinter PhotoImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary""" 544 return ImageTk.PhotoImage(self.asPil(name)) 545 546 def asTkBitmapImage(self, name: str): 547 """Create image as *monochrome* (two-color) tkinter BitmapImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary""" 548 mode_one_img = self.asPil(name).convert("1") 549 inverted_img = ImageOps.invert(mode_one_img) 550 return ImageTk.BitmapImage(inverted_img) 551 552 def asBytes(self, name: str, image_format: str="PNG"): 553 """Returns image data as bytestring, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF""" 554 with io.BytesIO() as output: 555 self.asPil(name).save(output, format=image_format) 556 return output.getvalue() 557 558 def asBytesIo(self, name: str, image_format: str="PNG"): 559 """Returns image data as BytesIO object, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF""" 560 output = io.BytesIO() 561 self.asPil(name).save(output, format=image_format) 562 output.seek(0) 563 return output 564 565 def asRawList(self, name: str, type: str="RGB"): 566 """Returns the pixel data of the image as a list. "name" must be a valid key for the codepoints dictionary, type="RGB" contains values 0-255, type="FLOAT" contains values 0-1""" 567 568 def _calc_pixel_value(value, type): 569 if type == "FLOAT": 570 return value / 255.0 571 return value # 'RGB' or any other type 572 573 icon = self.asPil(name) 574 pixel_data = [] 575 # Process image to list 576 # numpy etc. are WAY faster but introduce new dependencies 577 for i in range(0, icon.height): 578 for j in range(0, icon.width): 579 pixel = icon.getpixel((j, i)) 580 pixel_data.append(_calc_pixel_value(pixel[0], type)) 581 pixel_data.append(_calc_pixel_value(pixel[1], type)) 582 pixel_data.append(_calc_pixel_value(pixel[2], type)) 583 pixel_data.append(_calc_pixel_value(pixel[3], type)) 584 return pixel_data 585 586 def asQImage(self, name: str): 587 """Create image as QImage Object, "name" must be a valid key for the codepoints dictionary""" 588 return ImageQt.ImageQt(self.asPil(name)) 589 590 def asQPixmap(self, name: str): 591 """Create image as QPixmap Object, "name" must be a valid key for the codepoints dictionary""" 592 return ImageQt.toqpixmap(self.asPil(name)) 593 594 def asTempFile(self, name: str, extension: str="png"): 595 '''Returns a path to a temporary image file. If your framework only accepts file paths, you can use this function. The image format is determined by the file extension (Default is "png") and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary.''' 596 filepath = os.path.join(self._temp_dir.name, f'{str(uuid.uuid4())}.{extension.lower()}') 597 self.save(name, filepath) 598 return filepath 599 600 def save(self, name: str, save_as: str): 601 """Saves the icon to file "save_as", the image format is determined by the file extension and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary""" 602 kwargs={} 603 if save_as.lower().endswith('.ico'): 604 kwargs['sizes'] = [self._drawing_kwargs['icon_size']] 605 self.asPil(name).save(save_as, **kwargs) 606 607 def saveAll(self, save_to_dir: str, extension: str="png"): 608 '''Saves all icons in the icon set to path "save_to_dir", the image format is determined by the "extension" and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported.''' 609 for name in self._codepoints.keys(): 610 self.save(name, os.path.join(save_to_dir, f"{name}.{extension.lower()}")) 611 612 def show(self, name: str): 613 """Show the icon in an external viewer using the PIL Image.show() method. "name" must be a valid key for the codepoints dictionary""" 614 self.asPil(name).show()
Create an IconFactory for one of the icon sets included with iconipy. All icons created by this IconFactory will share the same settings, allowing you to change the style for all icons upon initialization.
icon_set (str): The name of the icon set that will be used to create the icon.
icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int)
font_size (int): The size of the font. Default is icon_size
font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string
outline_width (int): The width of the outline. 0 does not draw an outline
outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string
background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string
background_radius (int): The radius of the background corners.
259 def __init__( 260 self, 261 icon_set: str = "lucide", 262 icon_size: _SizeAttributeType = 64, 263 font_size: int = None, 264 font_color: _ColorAttributeType = "black", 265 outline_width: int = 0, 266 outline_color: _ColorAttributeType = "black", 267 background_color: _ColorAttributeType = None, 268 background_radius: int = 0, 269 ) -> None: 270 if not icon_set in _ICON_SETS.keys(): 271 raise ValueError(f'Unknown icon set "{icon_set}"') 272 273 self.icon_set_name = icon_set 274 '''Stores the name of the icon set that is used to create the icons''' 275 276 self.icon_set_version = self._get_icon_set_version( 277 _ICON_SETS[icon_set]["VERSION_FILE"] 278 ) 279 '''Stores the version string for the icon set''' 280 281 try: 282 self._codepoints = IconFactory._all_codepoints[icon_set] 283 except KeyError: 284 IconFactory._all_codepoints = self._read_codepoints() 285 self._codepoints = IconFactory._all_codepoints[icon_set] 286 287 self.icon_names = list(self._codepoints.keys()) 288 '''A list of all icon names for the selected icon set. When the documentation states that *"name" must be a valid key for the codepoints dictionary*, it means the name you enter must be included in this list.''' 289 290 self.license = self._get_license_text( 291 _ICON_SETS[icon_set]["LICENSE_FILE"] 292 ) 293 '''The icon set's license''' 294 font_size = self._check_font_vs_icon_size(font_size, icon_size) 295 296 self.icon_sets_available = list(_ICON_SETS.keys()) 297 '''A list containing all icon sets that are installed''' 298 299 self._drawing_kwargs = { 300 "font_path": _ICON_SETS[icon_set]["FONT_FILE"], 301 "icon_size": icon_size, 302 "font_size": font_size, 303 "font_color": font_color, 304 "icon_outline_width": outline_width, 305 "icon_outline_color": outline_color, 306 "icon_background_color": background_color, 307 "icon_background_radius": background_radius, 308 } 309 310 self._temp_dir = TemporaryDirectory()
A list of all icon names for the selected icon set. When the documentation states that "name" must be a valid key for the codepoints dictionary, it means the name you enter must be included in this list.
312 def changeIconSet(self, icon_set: str) -> list: 313 '''Change to a different icon set and retrieve a list containing the icon names 314 of the new set. Available icon sets include: lucide, boxicons, lineicons, material_icons_regular, 315 material_icons_round_regular, material_icons_sharp_regular, and material_icons_outlined_regular. 316 317 icon_set (str): The name of the icon set that will be used to create the icons. 318 ''' 319 if not icon_set in _ICON_SETS.keys(): 320 raise ValueError(f'Unknown icon set "{icon_set}"') 321 self.icon_set_name=icon_set 322 self._drawing_kwargs['font_path']=_ICON_SETS[icon_set]["FONT_FILE"] 323 self._codepoints = IconFactory._all_codepoints[icon_set] 324 self.icon_names = list(self._codepoints.keys()) 325 self.license = self._get_license_text( 326 _ICON_SETS[icon_set]["LICENSE_FILE"] 327 ) 328 self.icon_set_version = self._get_icon_set_version( 329 _ICON_SETS[icon_set]["VERSION_FILE"] 330 ) 331 return self.icon_names
Change to a different icon set and retrieve a list containing the icon names of the new set. Available icon sets include: lucide, boxicons, lineicons, material_icons_regular, material_icons_round_regular, material_icons_sharp_regular, and material_icons_outlined_regular.
icon_set (str): The name of the icon set that will be used to create the icons.
333 def updateCfg (self, 334 icon_size: _SizeAttributeType = None, 335 font_size: int = None, 336 font_color: _ColorAttributeType = None, 337 outline_width: int = None, 338 outline_color: _ColorAttributeType = None, 339 background_color: _ColorAttributeType = None, 340 background_radius: int = None, 341 ) -> dict: 342 '''Modify one or more parameters of the IconFactory object and retrieve a dictionary containing 343 the updated configuration. Typically, distinct IconFactories are created for different icon 344 styles, but there may be scenarios where reusing an existing object is desirable. 345 346 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 347 font_size (int): The size of the font. Default is icon_size 348 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 349 outline_width (int): The width of the outline. 0 does not draw an outline 350 outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string 351 background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string 352 background_radius (int): The radius of the background corners. 353 ''' 354 if not icon_size == None: 355 if isinstance(icon_size, int): 356 icon_size = icon_size if icon_size>0 else 1 357 elif isinstance(icon_size, tuple): 358 icon_size = (icon_size[0] if icon_size[0]>0 else 1, icon_size[1] if icon_size[1]>0 else 1) 359 self._drawing_kwargs['icon_size']=icon_size 360 if not font_size ==None: 361 self._drawing_kwargs['font_size']=self._check_font_vs_icon_size(font_size, icon_size) 362 if not font_color == None: 363 self._drawing_kwargs['font_color']=font_color 364 if not outline_width == None: 365 self._drawing_kwargs['icon_outline_width']=outline_width if outline_width>=0 else 0 366 if not outline_color == None: 367 self._drawing_kwargs['icon_outline_color']=outline_color 368 if not background_color == None: 369 self._drawing_kwargs['icon_background_color']=background_color 370 if not background_radius == None: 371 self._drawing_kwargs['icon_background_radius']=background_radius if background_radius>=0 else 0 372 373 return self._drawing_kwargs
Modify one or more parameters of the IconFactory object and retrieve a dictionary containing the updated configuration. Typically, distinct IconFactories are created for different icon styles, but there may be scenarios where reusing an existing object is desirable.
icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int)
font_size (int): The size of the font. Default is icon_size
font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string
outline_width (int): The width of the outline. 0 does not draw an outline
outline_color (str, tuple): The color of the outline. Name or RGBA-Tuple or hex string
background_color (str, tuple): The background color. Name or RGBA-Tuple or hex string
background_radius (int): The radius of the background corners.
528 def search(self, search_name: str) -> list: 529 """Search for an icon name. Returns a list of icon names containing the search_name""" 530 return [icon_name for icon_name in self.icon_names if search_name.lower() in icon_name.lower()]
Search for an icon name. Returns a list of icon names containing the search_name
532 def asPil(self, name: str): 533 """Create image as PIL Image Object, "name" must be a valid key for the codepoints dictionary""" 534 if not name in self._codepoints: 535 raise ValueError( 536 f'Icon with name "{name}" not available. Icon Set: {self.icon_set_name}, Version: {self.icon_set_version}' 537 ) 538 539 character = chr(int(self._codepoints[name], 16)) 540 return self._draw_character(character, **self._drawing_kwargs)
Create image as PIL Image Object, "name" must be a valid key for the codepoints dictionary
542 def asTkPhotoImage(self, name: str): 543 """Create image as tkinter PhotoImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary""" 544 return ImageTk.PhotoImage(self.asPil(name))
Create image as tkinter PhotoImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary
546 def asTkBitmapImage(self, name: str): 547 """Create image as *monochrome* (two-color) tkinter BitmapImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary""" 548 mode_one_img = self.asPil(name).convert("1") 549 inverted_img = ImageOps.invert(mode_one_img) 550 return ImageTk.BitmapImage(inverted_img)
Create image as monochrome (two-color) tkinter BitmapImage Object. Make sure you initialize tkinter first. Place your function call after creating the root instance (root = Tk() or equivalent for other GUI frameworks), "name" must be a valid key for the codepoints dictionary
552 def asBytes(self, name: str, image_format: str="PNG"): 553 """Returns image data as bytestring, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF""" 554 with io.BytesIO() as output: 555 self.asPil(name).save(output, format=image_format) 556 return output.getvalue()
Returns image data as bytestring, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF
558 def asBytesIo(self, name: str, image_format: str="PNG"): 559 """Returns image data as BytesIO object, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF""" 560 output = io.BytesIO() 561 self.asPil(name).save(output, format=image_format) 562 output.seek(0) 563 return output
Returns image data as BytesIO object, "name" must be a valid key for the codepoints dictionary, the image_format parameter should be set to one of the formats supported by Pillow. These formats include common image types like JPEG, PNG, ICO, and GIF
565 def asRawList(self, name: str, type: str="RGB"): 566 """Returns the pixel data of the image as a list. "name" must be a valid key for the codepoints dictionary, type="RGB" contains values 0-255, type="FLOAT" contains values 0-1""" 567 568 def _calc_pixel_value(value, type): 569 if type == "FLOAT": 570 return value / 255.0 571 return value # 'RGB' or any other type 572 573 icon = self.asPil(name) 574 pixel_data = [] 575 # Process image to list 576 # numpy etc. are WAY faster but introduce new dependencies 577 for i in range(0, icon.height): 578 for j in range(0, icon.width): 579 pixel = icon.getpixel((j, i)) 580 pixel_data.append(_calc_pixel_value(pixel[0], type)) 581 pixel_data.append(_calc_pixel_value(pixel[1], type)) 582 pixel_data.append(_calc_pixel_value(pixel[2], type)) 583 pixel_data.append(_calc_pixel_value(pixel[3], type)) 584 return pixel_data
Returns the pixel data of the image as a list. "name" must be a valid key for the codepoints dictionary, type="RGB" contains values 0-255, type="FLOAT" contains values 0-1
586 def asQImage(self, name: str): 587 """Create image as QImage Object, "name" must be a valid key for the codepoints dictionary""" 588 return ImageQt.ImageQt(self.asPil(name))
Create image as QImage Object, "name" must be a valid key for the codepoints dictionary
590 def asQPixmap(self, name: str): 591 """Create image as QPixmap Object, "name" must be a valid key for the codepoints dictionary""" 592 return ImageQt.toqpixmap(self.asPil(name))
Create image as QPixmap Object, "name" must be a valid key for the codepoints dictionary
594 def asTempFile(self, name: str, extension: str="png"): 595 '''Returns a path to a temporary image file. If your framework only accepts file paths, you can use this function. The image format is determined by the file extension (Default is "png") and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary.''' 596 filepath = os.path.join(self._temp_dir.name, f'{str(uuid.uuid4())}.{extension.lower()}') 597 self.save(name, filepath) 598 return filepath
Returns a path to a temporary image file. If your framework only accepts file paths, you can use this function. The image format is determined by the file extension (Default is "png") and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary.
600 def save(self, name: str, save_as: str): 601 """Saves the icon to file "save_as", the image format is determined by the file extension and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary""" 602 kwargs={} 603 if save_as.lower().endswith('.ico'): 604 kwargs['sizes'] = [self._drawing_kwargs['icon_size']] 605 self.asPil(name).save(save_as, **kwargs)
Saves the icon to file "save_as", the image format is determined by the file extension and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported. "name" must be a valid key for the codepoints dictionary
607 def saveAll(self, save_to_dir: str, extension: str="png"): 608 '''Saves all icons in the icon set to path "save_to_dir", the image format is determined by the "extension" and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported.''' 609 for name in self._codepoints.keys(): 610 self.save(name, os.path.join(save_to_dir, f"{name}.{extension.lower()}"))
Saves all icons in the icon set to path "save_to_dir", the image format is determined by the "extension" and should be set to one of the formats supported by Pillow. Only formats that support transparency (ico, png, gif, webp, jp2, ...) are supported.
612 def show(self, name: str): 613 """Show the icon in an external viewer using the PIL Image.show() method. "name" must be a valid key for the codepoints dictionary""" 614 self.asPil(name).show()
Show the icon in an external viewer using the PIL Image.show() method. "name" must be a valid key for the codepoints dictionary
617class CustomIconFactory(IconFactory): 618 """Create an IconFactory for a custom icon set by providing a font path 619 (supported by the FreeType library, e.g., TrueType, OpenType) and a 620 dictionary of codepoints. The dictionary keys should be the icon names 621 ('microphone'), and the values should be the corresponding 622 hexadecimal codepoints ('E02A'). 623 624 icon_set (str): The name of the icon set that will be used to create the icon. 625 icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int) 626 font_size (int): The size of the font. Default is icon_size 627 font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string 628 outline_width (int): The width of the outline. 0 does not draw an outline 629 outline_color (str, tuple): The color of the outline. Name, RGBA-Tuple or hex string 630 background_color (str, tuple): The background color. Name, RGBA-Tuple or hex string 631 background_radius (int): The radius of the background corners. 632 font_path (str): The path to the custom icon set font file. 633 codepoints (dict): A dictionary of icon names and codepoints. 634 version (str): The version of the icon set. 635 """ 636 637 def __init__( 638 self, 639 icon_set: str = "custom", 640 icon_size: _SizeAttributeType = 64, 641 font_size: int = None, 642 font_color: _ColorAttributeType = "black", 643 outline_width: int = 0, 644 outline_color: _ColorAttributeType = "black", 645 background_color: _ColorAttributeType = None, 646 background_radius: int = 0, 647 font_path: str = None, 648 codepoints: dict = None, 649 version: str = "0.1", 650 ) -> None: 651 if not font_path or not codepoints: 652 raise ValueError( 653 f'You need to supply a font path and codepoint dictionary"' 654 ) 655 656 self.icon_set_name = icon_set 657 '''Stores the name of the icon set''' 658 659 self.icon_set_version = version 660 '''Stores the version string for the icon set''' 661 662 self._codepoints = codepoints 663 664 self.icon_names = list(self._codepoints.keys()) 665 '''A list of all icon names for the selected icon set. When the documentation states that *"name" must be a valid key for the codepoints dictionary*, it means the name you enter must be included in this list.''' 666 667 self.license = 'Unknown License' 668 '''For custom icon sets the default value is "Unknown License"''' 669 670 font_size = self._check_font_vs_icon_size(font_size, icon_size) 671 672 self._drawing_kwargs = { 673 "font_path": font_path, 674 "font_size": font_size, 675 "font_color": font_color, 676 "icon_size": icon_size, 677 "icon_background_color": background_color, 678 "icon_outline_width": outline_width, 679 "icon_background_radius": background_radius, 680 "icon_outline_color": outline_color, 681 } 682 683 def changeIconSet(self, icon_set: str): 684 '''Not implemented for CustomIconFactory''' 685 raise NotImplementedError('changeIconSet is not implemented for CustomIconFactory')
Create an IconFactory for a custom icon set by providing a font path (supported by the FreeType library, e.g., TrueType, OpenType) and a dictionary of codepoints. The dictionary keys should be the icon names ('microphone'), and the values should be the corresponding hexadecimal codepoints ('E02A').
icon_set (str): The name of the icon set that will be used to create the icon.
icon_size (int, tuple): The size of the icons in pixels. Single int value or (int, int)
font_size (int): The size of the font. Default is icon_size
font_color (str, tuple): The color of the font. Name, RGBA-Tuple or hex string
outline_width (int): The width of the outline. 0 does not draw an outline
outline_color (str, tuple): The color of the outline. Name, RGBA-Tuple or hex string
background_color (str, tuple): The background color. Name, RGBA-Tuple or hex string
background_radius (int): The radius of the background corners.
font_path (str): The path to the custom icon set font file.
codepoints (dict): A dictionary of icon names and codepoints.
version (str): The version of the icon set.
637 def __init__( 638 self, 639 icon_set: str = "custom", 640 icon_size: _SizeAttributeType = 64, 641 font_size: int = None, 642 font_color: _ColorAttributeType = "black", 643 outline_width: int = 0, 644 outline_color: _ColorAttributeType = "black", 645 background_color: _ColorAttributeType = None, 646 background_radius: int = 0, 647 font_path: str = None, 648 codepoints: dict = None, 649 version: str = "0.1", 650 ) -> None: 651 if not font_path or not codepoints: 652 raise ValueError( 653 f'You need to supply a font path and codepoint dictionary"' 654 ) 655 656 self.icon_set_name = icon_set 657 '''Stores the name of the icon set''' 658 659 self.icon_set_version = version 660 '''Stores the version string for the icon set''' 661 662 self._codepoints = codepoints 663 664 self.icon_names = list(self._codepoints.keys()) 665 '''A list of all icon names for the selected icon set. When the documentation states that *"name" must be a valid key for the codepoints dictionary*, it means the name you enter must be included in this list.''' 666 667 self.license = 'Unknown License' 668 '''For custom icon sets the default value is "Unknown License"''' 669 670 font_size = self._check_font_vs_icon_size(font_size, icon_size) 671 672 self._drawing_kwargs = { 673 "font_path": font_path, 674 "font_size": font_size, 675 "font_color": font_color, 676 "icon_size": icon_size, 677 "icon_background_color": background_color, 678 "icon_outline_width": outline_width, 679 "icon_background_radius": background_radius, 680 "icon_outline_color": outline_color, 681 }
A list of all icon names for the selected icon set. When the documentation states that "name" must be a valid key for the codepoints dictionary, it means the name you enter must be included in this list.