Add skin tone modifiing scripts

This commit is contained in:
Constantin A 2018-04-04 21:17:53 +02:00
parent 9745b6d0c1
commit 6e9f9e3bbe
18 changed files with 879 additions and 0 deletions

17
skintone/blob/base.json Normal file
View file

@ -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"
}
}

16
skintone/blob/blob.json Normal file
View file

@ -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"
}
}

65
skintone/blobify.py Normal file
View file

@ -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()

116
skintone/emoji.py Normal file
View file

@ -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

View file

@ -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()

View file

@ -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()

151
skintone/gradient.py Normal file
View file

@ -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'<stop offset="((\.|[0-9])*)" style="stop-color:(#[1-9,A-F]*)"/>')
return regex.sub(r'<stop offset="\1" style="stop-color:{}"/>'.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"""<stop offset="((\.|[0-9])*)" style="stop-color:(#[1-9,A-F]*)"/>""", 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: <stop offset="{0}" style="stop-color:{2}"/>
stops.append(Stop(float(result[0]), result[2], float(prev), float(next_)))
return stops

189
skintone/modifier.py Normal file
View file

@ -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)

View file

@ -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)*)(<stop.*>)(( |\n)*<stop.*>)*(( |\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()

BIN
skintone/skins/.DS_Store vendored Normal file

Binary file not shown.

19
skintone/skins/base.json Normal file
View file

@ -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"
}
}

13
skintone/skins/dark.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "dark",
"extension": "1f3ff",
"colors":
{
"skin": "#70534a",
"hand": "#70534a",
"shadow": "#563e37",
"ear": "#563e37",
"hair": "#232020",
"pullover": "#00bfa5"
}
}

14
skintone/skins/light.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "light",
"extension": "1f3fb",
"colors":
{
"skin": "#fadcbc",
"hand": "#fadcbc",
"shadow": "#dba689",
"ear": "#dba689",
"hair": "#312d2d",
"hair_beard": "212121",
"pullover": "#00bfa5"
}
}

View file

@ -0,0 +1,13 @@
{
"name": "medium",
"extension": "1f3fd",
"colors":
{
"skin": "#bf8f68",
"hand": "#bf8f68",
"shadow": "#99674f",
"ear": "#99674f",
"hair": "#6d4c41",
"pullover": "#00bfa5"
}
}

View file

@ -0,0 +1,13 @@
{
"name": "medium_dark",
"extension": "1f3fe",
"colors":
{
"skin": "#9b643c",
"hand": "#9b643c",
"shadow": "#7a4c32",
"ear": "#7a4c32",
"hair": "#47352d",
"pullover": "#00bfa5"
}
}

View file

@ -0,0 +1,13 @@
{
"name": "medium_light",
"extension": "1f3fc",
"colors":
{
"skin": "#e0bb95",
"hand": "#e0bb95",
"shadow": "#c48e6a",
"ear": "#c48e6a",
"hair": "#bfa055",
"pullover": "#00bfa5"
}
}

View file

@ -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"
}
}

16
skintone/skins/white.json Normal file
View file

@ -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"
}
}