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}")
class IconFactory:
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.
IconFactory( icon_set: str = 'lucide', icon_size: Union[Tuple, int] = 64, font_size: int = None, font_color: Union[Tuple, str] = 'black', outline_width: int = 0, outline_color: Union[Tuple, str] = 'black', background_color: Union[Tuple, str] = None, background_radius: int = 0)
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()
icon_set_name

Stores the name of the icon set that is used to create the icons

icon_set_version

Stores the version string for the icon set

icon_names

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.

license

The icon set's license

icon_sets_available

A list containing all icon sets that are installed

def changeIconSet(self, icon_set: str) -> 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.
def updateCfg( self, icon_size: Union[Tuple, int] = None, font_size: int = None, font_color: Union[Tuple, str] = None, outline_width: int = None, outline_color: Union[Tuple, str] = None, background_color: Union[Tuple, str] = None, background_radius: int = None) -> dict:
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.
def search(self, search_name: str) -> list:
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

def asPil(self, name: str):
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

def asTkPhotoImage(self, name: str):
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

def asTkBitmapImage(self, name: str):
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

def asBytes(self, name: str, image_format: str = 'PNG'):
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

def asBytesIo(self, name: str, image_format: str = 'PNG'):
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

def asRawList(self, name: str, type: str = 'RGB'):
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

def asQImage(self, name: str):
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

def asQPixmap(self, name: str):
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

def asTempFile(self, name: str, extension: str = 'png'):
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.

def save(self, name: str, save_as: str):
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

def saveAll(self, save_to_dir: str, extension: str = 'png'):
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.

def show(self, name: str):
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

class CustomIconFactory(IconFactory):
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.
CustomIconFactory( icon_set: str = 'custom', icon_size: Union[Tuple, int] = 64, font_size: int = None, font_color: Union[Tuple, str] = 'black', outline_width: int = 0, outline_color: Union[Tuple, str] = 'black', background_color: Union[Tuple, str] = None, background_radius: int = 0, font_path: str = None, codepoints: dict = None, version: str = '0.1')
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        }
icon_set_name

Stores the name of the icon set

icon_set_version

Stores the version string for the icon set

icon_names

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.

license

For custom icon sets the default value is "Unknown License"

def changeIconSet(self, icon_set: str):
683    def changeIconSet(self, icon_set: str):
684        '''Not implemented for CustomIconFactory'''
685        raise NotImplementedError('changeIconSet is not implemented for CustomIconFactory') 

Not implemented for CustomIconFactory