diff --git a/skintone/blob/base.json b/skintone/blob/base.json
new file mode 100644
index 000000000..9b7be2cc3
--- /dev/null
+++ b/skintone/blob/base.json
@@ -0,0 +1,17 @@
+{
+ "name": "base",
+ "extension": "",
+ "tolerance": 5,
+ "colors":
+ {
+ "skin": "#FEE133",
+ "stroke": "#eb8f00",
+ "dark": "#242424",
+ "tongue": "#e04c74",
+ "tongue_dark": "#ab3d2e",
+ "blue": "#40c0e7",
+ "blue_stroke": "#47a9b0",
+ "water": "#5f7aff",
+ "water_stroke": "#4864ed"
+ }
+}
\ No newline at end of file
diff --git a/skintone/blob/blob.json b/skintone/blob/blob.json
new file mode 100644
index 000000000..4bdbe2b06
--- /dev/null
+++ b/skintone/blob/blob.json
@@ -0,0 +1,16 @@
+{
+ "name": "blob",
+ "extension": "blob",
+ "colors":
+ {
+ "skin": "#fcc21b",
+ "stroke": "#fcc21b",
+ "dark": "#2f2f2f",
+ "tongue": "#d7598b",
+ "tongue_dark": "#d7598b",
+ "blue": "#4fafd8",
+ "blue_stroke": "#4fafd8",
+ "water": "#ffffff",
+ "water_stroke": "#ffffff"
+ }
+}
\ No newline at end of file
diff --git a/skintone/blobify.py b/skintone/blobify.py
new file mode 100644
index 000000000..e5ad8e8ac
--- /dev/null
+++ b/skintone/blobify.py
@@ -0,0 +1,65 @@
+import generate_skincolor
+import remove_gradient
+import emoji
+import argparse
+import os
+
+
+def main():
+ """
+ Die main-Funktion, welche die Skin-Modifier verarbeitet
+ :return: Nix
+ """
+ # Alle Kommandozeilenargumente hinzufügen
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group(required = True)
+ group.add_argument('--input_file', '-i', help='Input file', metavar='ifile')
+ group.add_argument('--input_dir', '-d', help='Input directory', metavar='idir', default = '.')
+ parser.add_argument('--mod_dir', '-m', help='Modifier directory', metavar='mdir', default = './blob')
+ parser.add_argument('--base_name', '-b', help='Name of the base skin color', metavar='bname', default='base')
+ # Zu dict verarbeiten
+ args = vars(parser.parse_args())
+ # SKin-Modifier erstellen
+ modifiers = generate_skincolor.generate_modifiers(args['mod_dir'])
+ # Wurde ein Verzeichnis gewählt?
+ if args['input_dir']:
+ process_folder(args['input_dir'], modifiers, args['base_name'])
+ else:
+ process_file(modifiers, modifiers, args['base_name'])
+
+
+def process_folder(path: str, modifiers, base) -> None:
+ """
+ Entfernt die Verläufe für alle Dateien eines Ordners
+ :param path: Der Pfad zur Datei
+ :param modifiers: Die Modifikatoren
+ :param base: Der Basistyp
+ :return: Nix (ändert die Datei)
+ """
+ files = os.listdir(path)
+ errors = []
+ for file in files:
+ # Nur SVG wird derzeit unterstützt
+ if os.path.splitext(file)[-1].lower() in {'.svg'}:
+ err = process_file(os.path.join(path, file), modifiers, base, True)
+ if err:
+ errors.append(err)
+ print('Es sind {} Fehler aufgetreten bei folgenden Dateien:\n {}'.format(len(errors), '\n '.join(errors)))
+
+
+def process_file(path, modifiers, base, folder = False):
+ try:
+ # Entferne Verläufe
+ remove_gradient.process_file(path)
+ # Erstelle ein Emoji-Objekt
+ emoji_ = emoji.Emoji(modifiers, path, base)
+ # Und wende die Modifier an
+ emoji_.batch_modify()
+ except Exception as e:
+ print('Es ist ein Fehler aufgetreten beim Bearbeiten von: {}'.format(path))
+ print(e)
+ return path
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/skintone/emoji.py b/skintone/emoji.py
new file mode 100644
index 000000000..b5766cea1
--- /dev/null
+++ b/skintone/emoji.py
@@ -0,0 +1,116 @@
+from modifier import *
+import re
+
+
+class Emoji:
+
+ # There are some cases where an FE0F character has to be applied.
+ # This is the case for gender symbols which should be represented as emojis and not as characters
+ fe0f_chars = {'♀', '♂'}
+
+ def __init__(self, modifiers: dict, path: str, base: str, end: bool = False):
+ """
+ Create a new Emoji
+ :param modifiers: All skin modifiers available to this emoji
+ :param path: The path of the base SVG file
+ :param base: Name of the base type
+ :param end: True/False explicitly sets the FE0F character. fe0f_chars is used if None
+ """
+ # Assignments
+ self.modifiers = modifiers
+ self.path = path
+ self.directory = os.path.dirname(path)
+ self.base = modifiers[base]
+ self.name = os.path.splitext(os.path.basename(path))[0]
+ self.fextension = os.path.splitext(os.path.basename(path))[1]
+ self.content = self.load()
+ if end is not None:
+ # Explicit
+ self.end = end
+ else:
+ # Implicit
+ self.end = False
+ # Does it contain an "FE0F indicator"?
+ for char in Emoji.fe0f_chars:
+ if(char in path):
+ self.end = True
+ break
+
+ def batch_modify(self):
+ """
+ new_modified_file for all Modifiers
+ :return: None
+ """
+ for name, modifier in self.modifiers.items():
+ # You don't need to convert from base to base
+ if modifier != self.base:
+ self.new_modified_file(modifier)
+ print('{} auf Emoji {} angewendet.'.format(name, self.name))
+
+ def new_modified_file(self, modifier: Modifier):
+ """
+ Creates a new skin tone variant file
+ :param modifier: The Modifier
+ :return: None
+ """
+ # Update the SVG file
+ new_content = self.generate_modified(modifier)
+ # Save file
+ self.save(new_content, modifier.extension)
+
+ def load(self) -> str:
+ """Gets the (text) content of the base SVG file of this Emoji.
+ :return: the SVG file's content"""
+ try:
+ with open(self.path) as file:
+ return file.read()
+ except FileNotFoundError:
+ print('File "{}" not found!'.format(self.path))
+
+ def generate_modified(self, modifier: Modifier):
+ """
+ Creates a new skin tone variant of this Emoji
+ :param modifier: The Modifier which has to be applied
+ :return: The altered SVG file's content
+ """
+ # We're going to work on a copy of the content
+ content = self.content
+ # All colors are indexed by their name. We'll replace one by one
+ for old, new in modifier.replace(self.base).items():
+ # Create the regular expression which will be used
+ old_regex = re.compile(old, re.IGNORECASE)
+ # ...Apply it
+ content = old_regex.sub(new, content)
+ return content
+
+ def save(self, content: str, extension: str) -> None:
+ """
+ Save the new skin tone variant
+ :param content: The new content which has been created
+ :param extension: Any new characters which have to be added (usually 200d + the skin tone modifier)
+ :return: None (writes the file)
+ """
+ # Well, this should be obvious...
+ with open(self.generate_path(extension), 'w') as file:
+ file.write(content)
+
+ def generate_path(self, extension: str) -> str:
+ """
+ Creates the file path of the newly created variant
+ :param extension: All characters which have to be added to this emoji.
+ :return: A str containing the path/to/emoji_variant.svg
+ """
+ # Which directory? (It will be saved in the same one as the base emoji)
+ directory = self.directory
+ # Which is the base name of the file? (e.g. emoji_u1f973)
+ basename = self.name
+ # The file extension (.svg)
+ fileextension = self.fextension
+ base_seq = basename.split('_')
+ base_seq.insert(2, extension)
+ # Add FE0F?
+ if self.end:
+ base_seq.append('fe0f')
+ basename = '_'.join(base_seq)
+ # Stitch it together and return the whole file path
+ return os.path.join(directory, basename) + fileextension
diff --git a/skintone/generate_skincolor.py b/skintone/generate_skincolor.py
new file mode 100644
index 000000000..209cf5748
--- /dev/null
+++ b/skintone/generate_skincolor.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import argparse
+from modifier import *
+from emoji import *
+
+
+def main():
+ """
+ The main function doing all the work
+ :return: Nothing
+ """
+ # We'll need all those command line arguments
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group(required = True)
+ group.add_argument('--input_file', '-i', help='Input file.svg', metavar='ifile')
+ group.add_argument('--input_dir', '-d', help='Directory containing all base files.svg', metavar='idir', default = '.')
+ parser.add_argument('--mod_dir', '-m', help='Directory containing all modifier.json', metavar='mdir', default = './skins')
+ parser.add_argument('--base_name', '-b', help='Name of the base skin color (without file extensions)', metavar='bname', default='base')
+ parser.add_argument('--add_end', '-e', help='Do you want to add an fe0f ZWJ-sequence?', default='auto', choices=['y','n','auto'], required=False)
+ # Make a dict out of it
+ args = vars(parser.parse_args())
+ end = False if args['add_end'].lower() == 'n' else (True if args['add_end'].lower() == 'y' else None)
+ # Create skin-Modifiers
+ modifiers = generate_modifiers(args['mod_dir'])
+ # Did the user chose a dir or a file?
+ if args['input_dir']:
+ multi_process(args['input_dir'], modifiers, args['base_name'], end)
+ else:
+ # Create this one Emoji object
+ emoji = Emoji(modifiers, args['input_file'], args['base_name'], end)
+ # Apply modifiers
+ emoji.batch_modify()
+
+
+def generate_modifiers(path: str) -> dict:
+ """
+ Parses all skin modifiers in their directory
+ :param path: Directory containing the JSON files for the modifiers
+ :return: A str-Modifier dict containing the modifiers, sorted by their name (name: modifier)
+ """
+ modifiers = {}
+ for file in os.listdir(path):
+ # Ignore non-JSON files
+ if os.path.splitext(file)[-1].lower() == '.json':
+ # Create one Modifier out of this JSON file
+ modifier = Modifier.generate_from_json(os.path.join(path, file))
+ modifiers.update({modifier.name: modifier})
+ return modifiers
+
+
+def multi_process(directory: str, modifiers: dict, base: str, end: bool = False):
+ """
+ Processes one directory of Emoji files
+ :param directory: The directory containing the base SVG files
+ :param modifiers: All Modifiers (probably provided by generate_modifiers)
+ :param base: The name of the base skin color used in the base SVG files
+ :param end: If an FE0F sequence should be added
+ :return: Nothing (Files will be written)
+ """
+ files = os.listdir(directory)
+ for file in files:
+ # Ignore non-SVG-files
+ if os.path.splitext(file)[-1].lower() in {'.svg'}:
+ # Create a new Emoji-object
+ emoji = Emoji(modifiers, os.path.join(directory, file), base, end)
+ # Apply modifiers
+ emoji.batch_modify()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/skintone/generate_skincolor_de.py b/skintone/generate_skincolor_de.py
new file mode 100644
index 000000000..b2bc61582
--- /dev/null
+++ b/skintone/generate_skincolor_de.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import argparse
+from modifier import *
+from emoji import *
+
+
+def main():
+ """
+ Die main-Funktion, welche die Skin-Modifier verarbeitet
+ :return: Nix
+ """
+ # Alle Kommandozeilenargumente hinzufügen
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group(required = True)
+ group.add_argument('--input_file', '-i', help='Input file', metavar='ifile')
+ group.add_argument('--input_dir', '-d', help='Input directory', metavar='idir', default = '.')
+ parser.add_argument('--mod_dir', '-m', help='Modifier directory', metavar='mdir', default = './skins')
+ parser.add_argument('--base_name', '-b', help='Name of the base skin color', metavar='bname', default='base')
+ parser.add_argument('--add_end', '-e', help='Do you want to add an fe0f ZWJ-sequence?', default='n', choices=['y','n','auto'], required=False)
+ # Zu dict verarbeiten
+ args = vars(parser.parse_args())
+ end = False if args['add_end'].lower() == 'n' else (True if args['add_end'].lower() == 'y' else None)
+ # Skin-Modifier erstellen
+ modifiers = generate_modifiers(args['mod_dir'])
+ # Wurde ein Verzeichnis gewählt?
+ if args['input_dir']:
+ multi_process(args['input_dir'], modifiers, args['base_name'], end)
+ else:
+ # Erstelle ein Emoji-Objekt
+ emoji = Emoji(modifiers, args['input_file'], args['base_name'], end)
+ # Und wende die Modifier an
+ emoji.batch_modify()
+
+
+def generate_modifiers(path: str) -> dict:
+ """
+ Holt alle Skin-Modifier aus dem Ordner
+ :param path: Der Ordner mit den JSON-Dateien
+ :return: Ein dict mit name: Modifier
+ """
+ modifiers = {}
+ for file in os.listdir(path):
+ # Ist es überhaupt eine JSON-Datei?
+ if os.path.splitext(file)[-1].lower() == '.json':
+ # Erstelle aus der JSON-Datei und füge es ein
+ modifier = Modifier.generate_from_json(os.path.join(path, file))
+ modifiers.update({modifier.name: modifier})
+ return modifiers
+
+
+def multi_process(directory: str, modifiers: dict, base: str, end: bool = False):
+ """
+ Verarbeitet ein ganzes Verzeichnis mit Emojis
+ :param directory: Der Ordner
+ :param modifiers: Die Skin-Modifier
+ :param base: Der Name des Basis-Typen
+ :param end: Ob noch eine fe0f-Sequenz angefügt werden soll.
+ :return: Nix
+ """
+ files = os.listdir(directory)
+ for file in files:
+ # Nur SVG wird derzeit unterstützt
+ if os.path.splitext(file)[-1].lower() in {'.svg'}:
+ # Erstelle ein Emoji-Objekt
+ emoji = Emoji(modifiers, os.path.join(directory, file), base, end)
+ # Und wende die Modifier an
+ emoji.batch_modify()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/skintone/gradient.py b/skintone/gradient.py
new file mode 100644
index 000000000..3635a262b
--- /dev/null
+++ b/skintone/gradient.py
@@ -0,0 +1,151 @@
+from modifier import HexString
+import re
+
+
+class Gradient:
+ """
+ Ein Farbverlauf mit verschiedenen Stops
+ """
+ def __init__(self, stops: list, raw: str):
+ """
+ Erstelle einen neuen Farbverlauf
+ :param stops: Die einzelnen Stops
+ :param raw: Der ursprüngliche String
+ """
+ self.stops = stops
+ self.raw = raw
+
+ def calculate_average(self) -> str:
+ """
+ Berechnet die durchschnittliche Farbe mit Berücksichtigung der Offsets
+ :return: Die Farbe als String
+ """
+ # Platzhalter
+ av_color = []
+ colors = []
+ # Gehe jeden Stop durch
+ for stop in self.stops:
+ # Hole die einzelnen Farbkomponenten
+ components = stop.components
+ for i, component in enumerate(components):
+ # Hole die Länge/Breite ab
+ length = stop.length()
+ # Und berechne die Gewichtung
+ components[i] = length * component.value
+ colors.append(components)
+ # Jetzt sind die Komponenten wichtig
+ for component in zip(*colors):
+ av_color.append(HexString(hex(round(sum(component)))[2:]))
+ # Zum String machen
+ return '#' + ''.join([str(component) for component in av_color])
+
+ def first_color(self) -> str:
+ return self.stops[0].color
+
+ @staticmethod
+ def from_xml(in_str: str) -> list:
+ """
+ Erstelle Farbverläufe aus einem XML-String
+ :param in_str: Die XML-Datei als String
+ :return: Dei Farbverläufe als Liste
+ """
+ # Erstellt den reg. Ausdruck
+ regex = re.compile(r'(<[a-z]*Gradient .*>)((.|\n)+)([a-z]*Gradient>)', re.IGNORECASE)
+ # Sucht alle Farbverläufe raus
+ results = regex.findall(in_str)
+ resulting_gradients = []
+ # Erstellt die Objekte
+ for result in results:
+ stops = Stop.from_xml(result[1])
+ resulting_gradients.append(Gradient(stops, ''.join(result)))
+ return resulting_gradients
+
+ def replace_stops(self) -> str:
+ """
+ Ersetzt die Farben der Stops durch den Durchschnitt
+ :return: Der XML-String mit den Änderungen
+ """
+ regex = re.compile(r'')
+ return regex.sub(r''.format(self.first_color().upper()), self.raw)
+
+ def __str__(self):
+ return 'Farbverlauf mit {} Stops'.format(len(self.stops))
+
+ def __getitem__(self, key):
+ return self.stops[key]
+
+
+class Stop:
+ def __init__(self, offset: float, color: str, next_: float, prev: float):
+ """
+ Erstellt einen neuen Stop im Farbverlauf
+ :param offset: Die Position
+ :param color: Die Farbe als Hex-String
+ :param next_: Die nächste Position
+ :param prev: Die vorherige Position
+ """
+ self.next = next_
+ self.prev = prev
+ self.offset = offset
+ self.color = color
+
+ def __str__(self):
+ return 'Stop @ {}, Color: {}'.format(self.offset, self.color)
+
+ @property
+ def components(self) -> list:
+ """
+ Gibt die Komponenten als HexString aus
+ :return: Eine Liste von HexStrings (3 Stück; RGB)
+ """
+ if len(self.color) == 7: # Gibt nur RGB
+ return [HexString(self.color[1:3]), HexString(self.color[3:5]), HexString(self.color[5:7])]
+
+ @components.setter
+ def components(self, values):
+ self.color = '#' + ''.join(values)
+
+ def __len__(self) -> int:
+ """
+ Gibt an, wie breit der Streifen ist (als int, was i.d.R 0 ist)
+ :return: Die Breite als int
+ """
+ upper_ = (self.next + self.offset) // 2 # prev-------self---|---next
+ lower_ = (self.prev + self.offset) // 2 # prev---|---self-------next
+ return int(abs(upper_ - lower_))
+
+ def length(self) -> float:
+ """
+ Gibt die Breite des Streifens mit dieser Farbe zurück
+ :return: Die Breite als float
+ """
+ upper_ = (self.next + self.offset) / 2 # prev-------self---|---next
+ lower_ = (self.prev + self.offset) / 2 # prev---|---self-------next
+ return abs(upper_ - lower_) # prev---|==========|---next
+
+ @staticmethod
+ def from_xml(in_str: str) -> list:
+ """
+ Erstellt Stops aus einem XML-String
+ :param in_str: Der String
+ :return: Eine Liste mit Stop-Objekten
+ """
+ # reg. Ausdruck erstellen
+ regex = re.compile(r"""""", re.IGNORECASE)
+ results = regex.findall(in_str)
+ stops = []
+ # Parse die Resultate
+ for i, result in enumerate(results):
+ # Gibt es einen vorherigen Stop?
+ if i > 0:
+ prev = results[i-1][0]
+ else:
+ prev = 0
+ # Gibt es einen nachfolgenden Stop?
+ if i < len(results) - 1:
+ next_ = results[i+1][0]
+ else:
+ next_ = 1
+ # Das Muster ist:
+ stops.append(Stop(float(result[0]), result[2], float(prev), float(next_)))
+ return stops
\ No newline at end of file
diff --git a/skintone/modifier.py b/skintone/modifier.py
new file mode 100644
index 000000000..83b663566
--- /dev/null
+++ b/skintone/modifier.py
@@ -0,0 +1,189 @@
+import json
+import os
+
+
+class Modifier:
+ """An Object containing all relevant information about one skin tone Modifier"""
+
+ def __init__(self, name: str, colors: dict, extension: str, tolerance: int = 2, *args, **kwargs):
+ """
+ Creates a new skin tone modifier
+ :param name: The name of this skin tone
+ :param colors: All colors in 'name: color hex'-format
+ :param extension: All characters to be added to the original emoji sequence (e.g. _200d_1f3b0)
+ :param tolerance: The color 'radius' which is matched
+ """
+ self.name = name
+ self.colors = colors
+ self.extension = extension
+ self.tolerance = tolerance
+
+ def replace(self, base) -> dict:
+ """
+ Creates a dict containing all replacements
+ :param base: The base Modifier
+ :return: A dict containing the replacement rules as regular expressions
+ (e.g.: {'#(00|01|02)(00|01|02)(00|01|02)': '#ffffff', '#(10|11|12|13|14)(32|33|34|35|36)(54|55|56|57|58)': '#28923'}
+ """
+ # Create a new dict
+ replace = dict()
+ # Color: The color's name (e.g. 'skin')
+ # Value: The actual color code
+ for color, value in base.colors.items():
+ try:
+ # Try to find an appropiate replacement
+ replace.update({base.generate_tolerance(value): self.colors[color]})
+ except KeyError:
+ try:
+ # We'll now try to ignore any extensions added with '_'
+ # (e.g. "hand_2" -> "hand", "skin_boo_ya" -> "skin")
+ replace.update({base.generate_tolerance(value): self.colors[color.split('_')[0]]})
+ except KeyError:
+ # Replacement not found
+ print('Didn\'t find replacement for color {} from {} (Name: "{}" or "{}") in {}.'.format(value, base.name, color, color.split('_')[0], self.name))
+ return replace
+
+ def generate_tolerance(self, val: str) -> str:
+ """
+ Generates a color radius to get a little tolerance
+ Please note this is really bad code.
+ Even in comparison to the rest of this crap.
+ :param val: The color's hex code (e.g. #12345D)
+ :return: a regular expression covering this radius (e.g: #(10|11|12|13|14)(32|33|34|35|36)(5B|5C|5D|5E|5F))
+ """
+ if len(val) == 7: # RGB
+ pairs = [val[1:3], val[3:5], val[5:7]]
+ else: # RGBA
+ pairs = [val[1:3], val[3:5], val[5:7], val[7:9]]
+ # Placeholder for the new color components
+ new_pairs = []
+ for pair in pairs:
+ # Create a new Hex String with the two digits
+ hex = HexString(pair, 0, 0xff, 2)
+ # This one will contain all variations
+ # (42 will become [40,41,42,43,44])
+ vals = []
+ # Go through all possible values
+ for plus in range(-self.tolerance, self.tolerance + 1):
+ try:
+ # Try to add an offset
+ vals.append(hex + plus)
+ except ValueError:
+ # Ignore if the maximum range is exceeded
+ pass
+ # Apply the new values
+ new_pairs.append('({})'.format('|'.join((str(val) for val in vals))))
+ return '#' + ''.join(new_pairs)
+
+ @staticmethod
+ def generate_from_json(file: str):
+ """
+ Creates a new Modifier object out of a JSON file
+ :param file: The file path
+ :return: A new Modifier parsed from this JSON file
+ """
+ try:
+ # Open file
+ with open(file) as json_file:
+ # Load JSON table
+ jdict = json.loads(json_file.read())
+ # Do we have a name?
+ if 'name' in jdict:
+ return Modifier(**jdict)
+ else:
+ # If not, we'll just use the file name
+ return Modifier(name= os.path.splitext(os.path.basename(file))[0], **jdict)
+ except FileNotFoundError:
+ print("File not found ¯\_(ツ)_/¯")
+ except json.JSONDecodeError:
+ print("This is not a valid JSON file >:(")
+
+ def __str__(self):
+ return '{} (uxxxx_{}): Skin tone modifier with {} different colors'.format(self.name, self.extension, len(self.colors))
+
+ def detailed_info(self) -> str:
+ """
+ Returns more detailed information on this Modifier
+ :return: A str containing some details
+ """
+ return '{} (uxxxx_{}):\n {}'.format(self.name, self.extension, '\n '.join([': '.join(item) for item in list(self.colors.items())]))
+
+
+class HexString:
+ """
+ This is a simple data type to handle conversions and some basic operations on hexadecimal strings.
+ """
+
+ def __init__(self, string: str, min_: int = 0, max_: int = 0xff, length: int = 2):
+ """
+ Create a new HexString
+ :param string: The string representation without the 0x-prefix
+ :param min_: The min allowed value
+ :param max_: The max allowed value
+ :param length: The max zfill length
+ """
+ self.string = string.zfill(length)
+ self.value = int(string, 16)
+ self.min_ = min_
+ self.max_ = max_
+ self.length = length
+
+ def __add__(self, other):
+ """
+ Add another HexString or int
+ :param other: summand
+ :return: A new HexString with this operation applied
+ """
+ if type(other) == int:
+ # Add
+ result = HexString(hex(self.value + other)[2:], self.min_, self.max_)
+ # Test for range
+ if result.value in range(self.min_, self.max_ + 1):
+ return result
+ else:
+ raise ValueError('Value not in allowed range')
+ if type(other) == HexString:
+ # Add
+ result = HexString(hex(self.value + other.value)[2:], self.min_, self.max_)
+ # Test for range
+ if result.value in range(self.min_, self.max_ + 1):
+ return result
+ else:
+ raise ValueError('Value not in allowed range')
+
+ def __sub__(self, other):
+ """
+ Sub another HexString or int
+ :param other: Subtrahend
+ :return: A new HexString with this operation applied
+ """
+ if type(other) == int:
+ # Sub
+ result = HexString(hex(self.value - other)[2:], self.min_, self.max_)
+ # Test for range
+ if result.value in range(self.min_, self.max_ + 1):
+ return result
+ else:
+ raise ValueError('Value not in allowed range')
+ if type(other) == HexString:
+ # Sub
+ result = HexString(hex(self.value - other.value)[2:], self.min_, self.max_)
+ # Test for range
+ if result.value in range(self.min_, self.max_ + 1):
+ return result
+ else:
+ raise ValueError('Value not in allowed range')
+
+ def __mul__(self, other):
+ result = HexString(hex(self.value * other)[2:], self.min_, self.max_)
+ # Test for range
+ if result.value in range(self.min_, self.max_ + 1):
+ return result
+ else:
+ raise ValueError('Value not in allowed range')
+
+ def __len__(self):
+ return self.length
+
+ def __str__(self):
+ return self.string.zfill(self.length)
diff --git a/skintone/remove_gradient.py b/skintone/remove_gradient.py
new file mode 100644
index 000000000..325123925
--- /dev/null
+++ b/skintone/remove_gradient.py
@@ -0,0 +1,60 @@
+import argparse
+from gradient import *
+import os
+
+
+def main():
+ """
+ Die main-Funktion, welche die Farbverläufe entfernt
+ :return: Nix
+ """
+ # Alle Kommandozeilenargumente hinzufügen
+ parser = argparse.ArgumentParser()
+ group = parser.add_mutually_exclusive_group(required = True)
+ group.add_argument('--input_file', '-i', help='Input file', metavar='ifile')
+ group.add_argument('--input_dir', '-d', help='Input directory', metavar='idir', default = '.')
+ # Zu dict verarbeiten
+ args = vars(parser.parse_args())
+ if args['input_dir']:
+ process_folder(args['input_dir'])
+ else:
+ process_file(args['input_file'])
+
+
+def process_file(path: str) -> None:
+ """
+ Entfernt die Verläufe
+ :param path: Der Pfad zur Datei
+ :return: Nix (ändert die Datei)
+ """
+ with open(path, 'r') as file:
+ text = file.read()
+ # Erstelle den Reg. Ausdruck
+ regex = re.compile(
+ r'(<(linear|radial)Gradient .*>)(( |\n)*)()(( |\n)*)*(( |\n)*)((linear|radial)Gradient>)',
+ re.IGNORECASE)
+ text = regex.sub(r'\1\3\5\8\10', text)
+ with open(path, 'w') as file:
+ file.seek(0)
+ file.truncate()
+ file.write(text)
+
+
+def process_folder(path: str) -> None:
+ """
+ Entfernt die Verläufe für alle Dateien eines Ordners
+ :param path: Der Pfad zur Datei
+ :return: Nix (ändert die Datei)
+ """
+ files = os.listdir(path)
+ for file in files:
+ # Nur SVG wird derzeit unterstützt
+ if os.path.splitext(file)[-1].lower() in {'.svg'}:
+ print(path)
+ print(os.path.join(path, file))
+ process_file(os.path.join(path, file))
+
+
+if __name__ == '__main__':
+ main()
+
diff --git a/skintone/skins/.DS_Store b/skintone/skins/.DS_Store
new file mode 100644
index 000000000..701a3c903
Binary files /dev/null and b/skintone/skins/.DS_Store differ
diff --git a/skintone/skins/base.json b/skintone/skins/base.json
new file mode 100644
index 000000000..884fda3d5
--- /dev/null
+++ b/skintone/skins/base.json
@@ -0,0 +1,19 @@
+{
+ "name": "base",
+ "extension": "",
+ "colors":
+ {
+ "skin": "#fac01b",
+ "skin_2": "#ffb300",
+ "skin_3": "#fcc21b",
+ "hand": "#fbc11b",
+ "hand_2": "#fac036",
+ "shadow": "#e49800",
+ "shadow_2": "#e48c15",
+ "shadow_3": "#e59600",
+ "ear": "#e39400",
+ "hair": "#6d4c41",
+ "hair_beard": "#6d4c41",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/dark.json b/skintone/skins/dark.json
new file mode 100644
index 000000000..94f2ce5bd
--- /dev/null
+++ b/skintone/skins/dark.json
@@ -0,0 +1,13 @@
+{
+ "name": "dark",
+ "extension": "1f3ff",
+ "colors":
+ {
+ "skin": "#70534a",
+ "hand": "#70534a",
+ "shadow": "#563e37",
+ "ear": "#563e37",
+ "hair": "#232020",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/light.json b/skintone/skins/light.json
new file mode 100644
index 000000000..18ae0dfa0
--- /dev/null
+++ b/skintone/skins/light.json
@@ -0,0 +1,14 @@
+{
+ "name": "light",
+ "extension": "1f3fb",
+ "colors":
+ {
+ "skin": "#fadcbc",
+ "hand": "#fadcbc",
+ "shadow": "#dba689",
+ "ear": "#dba689",
+ "hair": "#312d2d",
+ "hair_beard": "212121",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/medium.json b/skintone/skins/medium.json
new file mode 100644
index 000000000..ebc3d72f5
--- /dev/null
+++ b/skintone/skins/medium.json
@@ -0,0 +1,13 @@
+{
+ "name": "medium",
+ "extension": "1f3fd",
+ "colors":
+ {
+ "skin": "#bf8f68",
+ "hand": "#bf8f68",
+ "shadow": "#99674f",
+ "ear": "#99674f",
+ "hair": "#6d4c41",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/medium_dark.json b/skintone/skins/medium_dark.json
new file mode 100644
index 000000000..da5e38e76
--- /dev/null
+++ b/skintone/skins/medium_dark.json
@@ -0,0 +1,13 @@
+{
+ "name": "medium_dark",
+ "extension": "1f3fe",
+ "colors":
+ {
+ "skin": "#9b643c",
+ "hand": "#9b643c",
+ "shadow": "#7a4c32",
+ "ear": "#7a4c32",
+ "hair": "#47352d",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/medium_light.json b/skintone/skins/medium_light.json
new file mode 100644
index 000000000..110e0991c
--- /dev/null
+++ b/skintone/skins/medium_light.json
@@ -0,0 +1,13 @@
+{
+ "name": "medium_light",
+ "extension": "1f3fc",
+ "colors":
+ {
+ "skin": "#e0bb95",
+ "hand": "#e0bb95",
+ "shadow": "#c48e6a",
+ "ear": "#c48e6a",
+ "hair": "#bfa055",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/redhead.json b/skintone/skins/redhead.json
new file mode 100644
index 000000000..f8360a0d5
--- /dev/null
+++ b/skintone/skins/redhead.json
@@ -0,0 +1,16 @@
+{
+ "name": "redhead",
+ "extension": "1f9b0",
+ "colors":
+ {
+ "skin": "#fac01b",
+ "hand": "#fbc11b",
+ "hand_2": "#fac036",
+ "shadow": "#e49800",
+ "shadow_2": "#e48c15",
+ "ear": "#e39400",
+ "hair": "#eb4a1e",
+ "hair_beard": "#e1450a",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file
diff --git a/skintone/skins/white.json b/skintone/skins/white.json
new file mode 100644
index 000000000..e27d4a12e
--- /dev/null
+++ b/skintone/skins/white.json
@@ -0,0 +1,16 @@
+{
+ "name": "white",
+ "extension": "1f9b3",
+ "colors":
+ {
+ "skin": "#fac01b",
+ "hand": "#fbc11b",
+ "hand_2": "#fac036",
+ "shadow": "#e49800",
+ "shadow_2": "#e48c15",
+ "ear": "#e39400",
+ "hair": "#ececec",
+ "hair_beard": "#f1f1f1",
+ "pullover": "#00bfa5"
+ }
+}
\ No newline at end of file