From 294308f913961db36268da652005b519b549faed Mon Sep 17 00:00:00 2001 From: Doug Felt Date: Wed, 5 Apr 2017 15:32:59 -0700 Subject: [PATCH 1/2] Add tool to generate emoji thumbnails. To generate the Unicode comparison page of various vendor emoji, Unicode prefers to use 72x72 images for all the supported emoji without aliasing. This tool will generate these from the directory of cleaned images produced by the emoji build, using the aliases defined in emoji_aliases.txt. --- generate_emoji_thumbnails.py | 126 +++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100755 generate_emoji_thumbnails.py diff --git a/generate_emoji_thumbnails.py b/generate_emoji_thumbnails.py new file mode 100755 index 000000000..f66fa6a3a --- /dev/null +++ b/generate_emoji_thumbnails.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# Copyright 2017 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Generate 72x72 thumbnails including aliases. + +Takes a source directory of images named using our emoji filename +conventions and writes thumbnails of them into the destination +directory. If a file is a target of one or more aliases, creates +copies named for the aliases.""" + + +import argparse +import collections +import logging +import os +from os import path +import shutil +import subprocess + +import add_aliases + +from nototools import tool_utils + +logger = logging.getLogger('emoji_thumbnails') + +def create_thumbnail(src_path, dst_path): + # uses imagemagik + subprocess.check_call(['convert', '-resize', '72x72', src_path, dst_path]) + logger.info('wrote thumbnail: %s' % dst_path) + + +_INV_ALIASES = None +def get_inv_aliases(): + global _INV_ALIASES + if _INV_ALIASES is None: + aliases = add_aliases.read_default_emoji_aliases() + inv_aliases = collections.defaultdict(list) + for k, v in aliases.iteritems(): + inv_aliases[v].append(k) + _INV_ALIASES = inv_aliases + return _INV_ALIASES + + +def is_emoji_filename(filename): + return filename.startswith('emoji_u') and filename.endswith('.png') + + +def check_emoji_filename(filename): + if not is_emoji_filename(filename): + raise ValueError('not an emoji image file: %s' % filename) + + +def emoji_filename_to_sequence(filename): + check_emoji_filename(filename) + + return tuple([ + int(s, 16) for s in filename[7:-4].split('_') + if s.lower() != 'fe0f']) + + +def sequence_to_emoji_filename(seq): + return 'emoji_u%s.png' % '_'.join('%04x' % n for n in seq) + + +def create_emoji_alias(src_path, dst_dir): + """If src_path is a target of any emoji aliases, create a copy in dst_dir + named for each alias.""" + src_file = path.basename(src_path) + seq = emoji_filename_to_sequence(src_file) + + inv_aliases = get_inv_aliases() + if seq in inv_aliases: + for alias_seq in inv_aliases[seq]: + alias_file = sequence_to_emoji_filename(alias_seq) + shutil.copy2(src_path, path.join(dst_dir, alias_file)) + logger.info('wrote alias %s (copy of %s)' % (alias_file, src_file)) + + +def create_thumbnails_and_aliases(src_dir, dst_dir): + if not path.isdir(src_dir): + raise ValueError('"%s" is not a directory') + dst_dir = tool_utils.ensure_dir_exists(dst_dir) + + for f in os.listdir(src_dir): + if not (f.startswith('emoji_u') and f.endswith('.png')): + logger.warning('skipping "%s"' % f) + continue + src = path.join(src_dir, f) + dst = path.join(dst_dir, f) + create_thumbnail(src, dst) + create_emoji_alias(dst, dst_dir) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '-s', '--src_dir', help='source images', metavar='dir', required=True) + parser.add_argument( + '-d', '--dst_dir', help='destination directory', metavar='dir', + required=True) + parser.add_argument( + '-v', '--verbose', help='write log output', metavar='level', + choices='warning info debug'.split(), const='info', + nargs='?') + args = parser.parse_args() + + if args.verbose is not None: + logging.basicConfig(level=getattr(logging, args.verbose.upper())) + + create_thumbnails_and_aliases(args.src_dir, args.dst_dir) + + +if __name__ == '__main__': + main() From 7ce8ee5d738210302c7d9eea2934943efc8bfaa1 Mon Sep 17 00:00:00 2001 From: Doug Felt Date: Mon, 10 Apr 2017 13:43:00 -0700 Subject: [PATCH 2/2] Update thumbnail generation and add unknown flag sequences. Thumbnail generation for Unicode requires some changes: - 72x72 images (exact, not just fit within that frame) - custom prefix ending in underscore - images for unsupported flags The default build doesn't support some flags by default, since they are not wanted by Android. For the thumbnail list these images need to be provided, so we alias them to the unknown flag images as that's what would show for them. It's probably a good idea to list these explicitly anyway, other tools could use this information. --- generate_emoji_thumbnails.py | 115 ++++++++++++++++++++--------------- unknown_flag_aliases.txt | 25 ++++++++ 2 files changed, 90 insertions(+), 50 deletions(-) create mode 100644 unknown_flag_aliases.txt diff --git a/generate_emoji_thumbnails.py b/generate_emoji_thumbnails.py index f66fa6a3a..8ca9d4523 100755 --- a/generate_emoji_thumbnails.py +++ b/generate_emoji_thumbnails.py @@ -32,75 +32,86 @@ import subprocess import add_aliases from nototools import tool_utils +from nototools import unicode_data logger = logging.getLogger('emoji_thumbnails') def create_thumbnail(src_path, dst_path): # uses imagemagik - subprocess.check_call(['convert', '-resize', '72x72', src_path, dst_path]) - logger.info('wrote thumbnail: %s' % dst_path) + # we need imagex exactly 72x72 in size, with transparent background + subprocess.check_call([ + 'convert', '-thumbnail', '72x72', '-gravity', 'center', '-background', + 'none', '-extent', '72x72', src_path, dst_path]) -_INV_ALIASES = None def get_inv_aliases(): - global _INV_ALIASES - if _INV_ALIASES is None: - aliases = add_aliases.read_default_emoji_aliases() - inv_aliases = collections.defaultdict(list) - for k, v in aliases.iteritems(): - inv_aliases[v].append(k) - _INV_ALIASES = inv_aliases - return _INV_ALIASES + """Return a mapping from target to list of sources for all alias + targets in either the default alias table or the unknown_flag alias + table.""" + + inv_aliases = collections.defaultdict(list) + + standard_aliases = add_aliases.read_default_emoji_aliases() + for k, v in standard_aliases.iteritems(): + inv_aliases[v].append(k) + + unknown_flag_aliases = add_aliases.read_emoji_aliases( + 'unknown_flag_aliases.txt') + for k, v in unknown_flag_aliases.iteritems(): + inv_aliases[v].append(k) + + return inv_aliases -def is_emoji_filename(filename): - return filename.startswith('emoji_u') and filename.endswith('.png') +def filename_to_sequence(filename, prefix, suffix): + if not filename.startswith(prefix) and filename.endswith(suffix): + raise ValueError('bad prefix or suffix: "%s"' % filename) + seq_str = filename[len(prefix): -len(suffix)] + seq = unicode_data.string_to_seq(seq_str) + if not unicode_data.is_cp_seq(seq): + raise ValueError('sequence includes non-codepoint: "%s"' % filename) + return seq -def check_emoji_filename(filename): - if not is_emoji_filename(filename): - raise ValueError('not an emoji image file: %s' % filename) +def sequence_to_filename(seq, prefix, suffix): + return ''.join((prefix, unicode_data.seq_to_string(seq), suffix)) -def emoji_filename_to_sequence(filename): - check_emoji_filename(filename) +def create_thumbnails_and_aliases(src_dir, dst_dir, dst_prefix): + """Creates thumbnails in dst_dir based on sources in src.dir, using + dst_prefix. Assumes the source prefix is 'emoji_u' and the common suffix + is '.png'.""" - return tuple([ - int(s, 16) for s in filename[7:-4].split('_') - if s.lower() != 'fe0f']) - - -def sequence_to_emoji_filename(seq): - return 'emoji_u%s.png' % '_'.join('%04x' % n for n in seq) - - -def create_emoji_alias(src_path, dst_dir): - """If src_path is a target of any emoji aliases, create a copy in dst_dir - named for each alias.""" - src_file = path.basename(src_path) - seq = emoji_filename_to_sequence(src_file) - - inv_aliases = get_inv_aliases() - if seq in inv_aliases: - for alias_seq in inv_aliases[seq]: - alias_file = sequence_to_emoji_filename(alias_seq) - shutil.copy2(src_path, path.join(dst_dir, alias_file)) - logger.info('wrote alias %s (copy of %s)' % (alias_file, src_file)) - - -def create_thumbnails_and_aliases(src_dir, dst_dir): if not path.isdir(src_dir): raise ValueError('"%s" is not a directory') dst_dir = tool_utils.ensure_dir_exists(dst_dir) - for f in os.listdir(src_dir): - if not (f.startswith('emoji_u') and f.endswith('.png')): - logger.warning('skipping "%s"' % f) + src_prefix = 'emoji_u' + suffix = '.png' + + inv_aliases = get_inv_aliases() + + for src_file in os.listdir(src_dir): + try: + seq = unicode_data.strip_emoji_vs( + filename_to_sequence(src_file, src_prefix, suffix)) + except ValueError as ve: + logger.warning('Error (%s), skipping' % ve) continue - src = path.join(src_dir, f) - dst = path.join(dst_dir, f) - create_thumbnail(src, dst) - create_emoji_alias(dst, dst_dir) + + src_path = path.join(src_dir, src_file) + + dst_file = sequence_to_filename(seq, dst_prefix, suffix) + dst_path = path.join(dst_dir, dst_file) + + create_thumbnail(src_path, dst_path) + logger.info('wrote thumbnail: %s' % dst_file) + + for alias_seq in inv_aliases.get(seq, ()): + alias_file = sequence_to_filename(alias_seq, dst_prefix, suffix) + alias_path = path.join(dst_dir, alias_file) + shutil.copy2(dst_path, alias_path) + logger.info('wrote alias: %s' % alias_file) def main(): @@ -110,6 +121,9 @@ def main(): parser.add_argument( '-d', '--dst_dir', help='destination directory', metavar='dir', required=True) + parser.add_argument( + '-p', '--prefix', help='prefix for thumbnail', metavar='str', + default='android_') parser.add_argument( '-v', '--verbose', help='write log output', metavar='level', choices='warning info debug'.split(), const='info', @@ -119,7 +133,8 @@ def main(): if args.verbose is not None: logging.basicConfig(level=getattr(logging, args.verbose.upper())) - create_thumbnails_and_aliases(args.src_dir, args.dst_dir) + create_thumbnails_and_aliases( + args.src_dir, args.dst_dir, args.prefix) if __name__ == '__main__': diff --git a/unknown_flag_aliases.txt b/unknown_flag_aliases.txt new file mode 100644 index 000000000..3708463ab --- /dev/null +++ b/unknown_flag_aliases.txt @@ -0,0 +1,25 @@ +# alias table +# from;to +# the 'from' sequence should be represented by the image for the 'to' sequence +# these are flags we explicitly omit + +# flag aliases +1f1e7_1f1f1;fe82b # BL +1f1e7_1f1f6;fe82b # BQ +1f1e9_1f1ec;fe82b # DG +1f1ea_1f1e6;fe82b # EA +1f1ea_1f1ed;fe82b # EH +1f1eb_1f1f0;fe82b # FK +1f1ec_1f1eb;fe82b # GF +1f1ec_1f1f5;fe82b # GP +1f1ec_1f1f8;fe82b # GS +1f1f2_1f1eb;fe82b # MF +1f1f2_1f1f6;fe82b # MQ +1f1f3_1f1e8;fe82b # NC +1f1f5_1f1f2;fe82b # PM +1f1f7_1f1ea;fe82b # RE +1f1f9_1f1eb;fe82b # TF +1f1fc_1f1eb;fe82b # WF +1f1fd_1f1f0;fe82b # XK +1f1fe_1f1f9;fe82b # YT +