Changeset View
Changeset View
Standalone View
Standalone View
source/blender/datatoc/datatoc_icon_split.py
| Show All 13 Lines | |||||
| # along with this program; if not, write to the Free Software Foundation, | # along with this program; if not, write to the Free Software Foundation, | ||||
| # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| # | # | ||||
| # ##### END GPL LICENSE BLOCK ##### | # ##### END GPL LICENSE BLOCK ##### | ||||
| # <pep8 compliant> | # <pep8 compliant> | ||||
| """ | """ | ||||
| This script dices up PNG into small files to store in version control. | This script converts PNG into .dat files to store in version control. | ||||
| Example: | Example: | ||||
| ./blender.bin \ | ./blender.bin \ | ||||
| --background -noaudio \ | --background -noaudio \ | ||||
| --python ./release/datafiles/icon_dice.py -- \ | --python ./source/blender/datatoc/datatoc_icon_split.py -- \ | ||||
| --image=./release/datafiles/blender_icons16.png \ | --source-dir=./release/datafiles/blender_icons16 \ | ||||
| --output=./release/datafiles/blender_icons16 | --output-dir=./release/datafiles/blender_icons16 \ | ||||
| --output_prefix=icon16_ | --file-prefix=icon16_ \ | ||||
| --name_style=UI_ICONS | --svg-dir=./release/datafiles/blender_icons | ||||
| --parts_x 26 --parts_y 32 \ | |||||
| --minx=10 --maxx 10 --miny 10 --maxy 10 | |||||
| --minx_icon 2 --maxx_icon 2 --miny_icon 2 --maxy_icon 2 \ | |||||
| --spacex_icon 1 --spacey_icon 1 | |||||
| """ | """ | ||||
| import os | |||||
| SOURCE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) | |||||
| VERBOSE = False | |||||
| def image_from_file__bpy(filepath): | def image_from_file__bpy(filepath): | ||||
| import bpy | import bpy | ||||
| image = bpy.data.images.load(filepath) | image = bpy.data.images.load(filepath) | ||||
| image.reload() | image.reload() | ||||
| pixel_w, pixel_h = image.size | pixel_w, pixel_h = image.size | ||||
| pixels = image.pixels[:] | pixels = image.pixels[:] | ||||
| Show All 15 Lines | def image_from_file(filepath): | ||||
| if bpy is not None: | if bpy is not None: | ||||
| pixels, pixel_w, pixel_h = image_from_file__bpy(filepath) | pixels, pixel_w, pixel_h = image_from_file__bpy(filepath) | ||||
| # else: | # else: | ||||
| # pixels, pixel_w, pixel_h = image_from_file__py(filepath) | # pixels, pixel_w, pixel_h = image_from_file__py(filepath) | ||||
| return pixels, pixel_w, pixel_h | return pixels, pixel_w, pixel_h | ||||
| def write_subimage(sub_x, sub_y, sub_w, sub_h, | def write_subimage(filepath, | ||||
| filepath, | pixels, pixel_w, pixel_h, | ||||
| pixels, pixel_w, pixel_h): | svg_hash): | ||||
| import struct | import struct | ||||
| # first check if the icon is worth writing | # Is the icon color or monochrome with white color only. | ||||
| is_fill = False | is_color = False | ||||
| for y in range(sub_h): | |||||
| for x in range(sub_w): | |||||
| i = (sub_x + x) + ((sub_y + y) * pixel_w) | |||||
| a = pixels[(i * 4) + 3] | |||||
| if a != 0.0: | |||||
| is_fill = True | |||||
| break | |||||
| if not is_fill: | |||||
| # print("skipping:", filepath) | |||||
| return | |||||
| with open(filepath, 'wb') as f: | with open(filepath, 'wb') as f: | ||||
| f.write( | f.write( | ||||
| struct.pack( | struct.pack( | ||||
| '<6I', | '<2I', | ||||
| sub_w, sub_h, | |||||
| sub_x, sub_y, | |||||
| # redundant but including to maintain consistency | |||||
| pixel_w, pixel_h, | pixel_w, pixel_h, | ||||
| )) | )) | ||||
| for y in range(sub_h): | f.write(svg_hash) | ||||
| for x in range(sub_w): | |||||
| i = (sub_x + x) + ((sub_y + y) * pixel_w) | for y in range(pixel_h): | ||||
| for x in range(pixel_w): | |||||
| i = x + y * pixel_w | |||||
| if is_color: | |||||
| rgba = pixels[(i * 4):(i * 4) + 4] | rgba = pixels[(i * 4):(i * 4) + 4] | ||||
| c = sum((int(p * 255) << (8 * i)) for i, p in enumerate(rgba)) | c = sum((int(p * 255) << (8 * i)) for i, p in enumerate(rgba)) | ||||
| f.write(struct.pack("<I", c)) | else: | ||||
| a = pixels[(i * 4) + 3] | |||||
| c = (int(a * 255) << (8 * 3)) + 16777215 if a > 0 else 0 | |||||
| f.write(struct.pack('<I', c)) | |||||
| _dice_icon_name_cache = {} | def icon_decode_head(f_src): | ||||
| import struct | |||||
| temp_data = f_src.read(4 * 2) | |||||
| icon_w, icon_h = struct.unpack('<2I', temp_data) | |||||
| def dice_icon_name( | svg_hash = f_src.read(16) | ||||
| x, y, parts_x, parts_y, | |||||
| name_style=None, prefix=""): | |||||
| """ | |||||
| How to name icons, this is mainly for what name we get in git, | |||||
| the actual names don't really matter, its just nice to have the | |||||
| name match up with something recognizable for commits. | |||||
| """ | |||||
| if name_style == 'UI_ICONS': | |||||
| # Init on demand | return (icon_w, icon_h, svg_hash) | ||||
| if not _dice_icon_name_cache: | |||||
| def get_icon_names(): | |||||
| import os | |||||
| import re | import re | ||||
| count = 0 | |||||
| # Search for eg: DEF_ICON(BRUSH_NUDGE) --> BRUSH_NUDGE | # Search for, e.g. "DEF_ICON(BRUSH_NUDGE)" and get "BRUSH_NUDGE". | ||||
| re_icon = re.compile(r'^\s*DEF_ICON.*\(\s*([A-Za-z0-9_]+)\s*\).*$') | re_icon = re.compile(r'^\s*DEF_ICON.*\(\s*([A-Za-z0-9_]+)\s*\).*$') | ||||
| icon_name_cache = {} | |||||
| count = 0 | |||||
| SOURCE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) | |||||
| ui_icons_h = os.path.join(SOURCE_DIR, "source", "blender", "editors", "include", "UI_icons.h") | ui_icons_h = os.path.join(SOURCE_DIR, "source", "blender", "editors", "include", "UI_icons.h") | ||||
| with open(ui_icons_h, 'r', encoding="utf-8") as f: | with open(ui_icons_h, 'r', encoding="utf-8") as f: | ||||
| for l in f: | for l in f: | ||||
| match = re_icon.search(l) | match = re_icon.search(l) | ||||
| if match: | if match: | ||||
| if l.find('DEF_ICON_BLANK') == -1: | if l.find('DEF_ICON_BLANK') == -1: | ||||
| icon_name = match.group(1).lower() | icon_name = match.group(1).lower() | ||||
| print(icon_name) | # print(icon_name) | ||||
| _dice_icon_name_cache[count] = icon_name | if icon_name not in ("none", "blank1"): | ||||
| icon_name_cache[count] = icon_name | |||||
| count += 1 | count += 1 | ||||
| # ---- Done with icon cache | |||||
| index = (y * parts_x) + x | return icon_name_cache | ||||
| if index not in _dice_icon_name_cache: | |||||
| return None | |||||
| icon_name = _dice_icon_name_cache[index] | |||||
| # for debugging its handy to sort by number | def main_ex(source_dir, output_dir, file_prefix, svg_dir): | ||||
| # ~ id_str = "%03d_%s%s.dat" % (index, prefix, icon_name) | import os | ||||
| import hashlib | |||||
| import struct | |||||
| id_str = "%s%s.dat" % (prefix, icon_name) | icon_name_cache = get_icon_names() | ||||
| elif name_style == "": | # Match the definition in 'interface_icons.c'. | ||||
| # flip so icons are numbered from top-left | ICON_GRID_COLS = 26 | ||||
| # because new icons will be added at the bottom | ICON_GRID_ROWS = 30 | ||||
| y_flip = parts_y - (y + 1) | |||||
| id_str = "%s%02xx%02x.dat" % (prefix, x, y_flip) | |||||
| else: | |||||
| raise Exception("Invalid '--name_style' arg") | |||||
| return id_str | for i in range(ICON_GRID_COLS * ICON_GRID_ROWS): | ||||
| if i not in icon_name_cache: | |||||
| continue | |||||
| id_str = icon_name_cache[i] | |||||
| svg_path = os.path.join(svg_dir, "%s.svg" % id_str) | |||||
| source_path = os.path.join(source_dir, "%s%s.png" % (file_prefix, id_str)) | |||||
| output_path = os.path.join(output_dir, "%s%s.dat" % (file_prefix, id_str)) | |||||
| # The original svg file checksum. | |||||
| if os.path.exists(svg_path): | |||||
| svg_hash = hashlib.md5(open(svg_path,'rb').read()).digest() | |||||
| check = True | |||||
| else: | |||||
| svg_hash = struct.pack('<16x') | |||||
| check = False | |||||
| def dice( | # Check if the icon is worth writing. | ||||
| filepath, output, output_prefix, name_style, | if check and os.path.exists(output_path): | ||||
| parts_x, parts_y, | with open(output_path, 'rb') as f: | ||||
| minx, miny, maxx, maxy, | head, pixels, old_hash = icon_decode_head(f) | ||||
| minx_icon, miny_icon, maxx_icon, maxy_icon, | |||||
| spacex_icon, spacey_icon, | |||||
| ): | |||||
| is_simple = (max( | if svg_hash == old_hash: | ||||
| minx, miny, maxx, maxy, | # print("skipping:", output_path) | ||||
| minx_icon, miny_icon, maxx_icon, maxy_icon, | continue | ||||
| spacex_icon, spacey_icon) == 0) | |||||
| pixels, pixel_w, pixel_h = image_from_file(filepath) | # Reading the input file. | ||||
| if os.path.exists(source_path): | |||||
| pixels, pixel_w, pixel_h = image_from_file(source_path) | |||||
| else: | |||||
| print("File not found: %r" % source_path) | |||||
| return | |||||
| if not (pixel_w and pixel_h): | if not (pixel_w and pixel_h): | ||||
| print("Image not found %r!" % filepath) | print("Cannot load image: %r" % source_path) | ||||
| return | return | ||||
| if not os.path.exists(output): | # Writing the output file. | ||||
| os.mkdir(output) | if not os.path.exists(output_dir): | ||||
| os.mkdir(output_dir) | |||||
| if is_simple: | |||||
| pixels_w_clip = pixel_w | |||||
| pixels_h_clip = pixel_h | |||||
| icon_w = pixels_w_clip // parts_x | |||||
| icon_h = pixels_h_clip // parts_y | |||||
| icon_w_clip = icon_w | |||||
| icon_h_clip = icon_h | |||||
| else: | |||||
| pixels_w_clip = pixel_w - (minx + maxx) | |||||
| pixels_h_clip = pixel_h - (miny + maxy) | |||||
| icon_w = (pixels_w_clip - ((parts_x - 1) * spacex_icon)) // parts_x | |||||
| icon_h = (pixels_h_clip - ((parts_y - 1) * spacey_icon)) // parts_y | |||||
| icon_w_clip = icon_w - (minx_icon + maxx_icon) | |||||
| icon_h_clip = icon_h - (miny_icon + maxy_icon) | |||||
| print(pixel_w, pixel_h, icon_w, icon_h) | |||||
| for x in range(parts_x): | |||||
| for y in range(parts_y): | |||||
| id_str = dice_icon_name( | |||||
| x, y, | |||||
| parts_x, parts_y, | |||||
| name_style=name_style, prefix=output_prefix | |||||
| ) | |||||
| if not id_str: | |||||
| continue | |||||
| filepath = os.path.join(output, id_str) | |||||
| if VERBOSE: | |||||
| print(" writing:", filepath) | |||||
| # simple, no margins | |||||
| if is_simple: | |||||
| sub_x = x * icon_w | |||||
| sub_y = y * icon_h | |||||
| else: | |||||
| sub_x = minx + ((x * (icon_w + spacex_icon)) + minx_icon) | |||||
| sub_y = miny + ((y * (icon_h + spacey_icon)) + miny_icon) | |||||
| write_subimage(sub_x, sub_y, icon_w_clip, icon_h_clip, | # print("writing:", output_path) | ||||
| filepath, | write_subimage(output_path, pixels, pixel_w, pixel_h, svg_hash) | ||||
| pixels, pixel_w, pixel_h) | |||||
| def main(): | def main(): | ||||
| import sys | import sys | ||||
| import argparse | import argparse | ||||
| epilog = "Run this after updating the SVG file" | epilog = "Run this after updating the SVG file" | ||||
| argv = sys.argv | argv = sys.argv | ||||
| if "--" not in argv: | if "--" not in argv: | ||||
| argv = [] | argv = [] | ||||
| else: | else: | ||||
| argv = argv[argv.index("--") + 1:] | argv = argv[argv.index("--") + 1:] | ||||
| parser = argparse.ArgumentParser(description=__doc__, epilog=epilog) | parser = argparse.ArgumentParser(description=__doc__, epilog=epilog) | ||||
| # File path options | |||||
| parser.add_argument( | |||||
| "--image", dest="image", metavar='FILE', | |||||
| help="Image file", | |||||
| ) | |||||
| parser.add_argument( | |||||
| "--output", dest="output", metavar='DIR', | |||||
| help="Output directory", | |||||
| ) | |||||
| parser.add_argument( | parser.add_argument( | ||||
| "--output_prefix", dest="output_prefix", metavar='STRING', | "--source-dir", dest="src", metavar='DIR', | ||||
| help="Output prefix", | help="Source directory with PNG files", | ||||
| ) | ) | ||||
| # Icon naming option | |||||
| parser.add_argument( | parser.add_argument( | ||||
| "--name_style", dest="name_style", metavar='ENUM', type=str, | "--output-dir", dest="out", metavar='DIR', | ||||
| choices=('', 'UI_ICONS'), | help="Output directory for icon files", | ||||
| help="The method used for naming output data", | |||||
| ) | ) | ||||
| # Options for dicing up the image | |||||
| parser.add_argument( | parser.add_argument( | ||||
| "--parts_x", dest="parts_x", metavar='INT', type=int, | "--file-prefix", dest="prefix", metavar='STRING', | ||||
| help="Grid X parts", | help="File name prefix", | ||||
| ) | ) | ||||
| parser.add_argument( | parser.add_argument( | ||||
| "--parts_y", dest="parts_y", metavar='INT', type=int, | "--svg-dir", dest="svg", metavar='DIR', | ||||
| help="Grid Y parts", | help="Directory with SVG files", | ||||
| ) | ) | ||||
| _help = "Inset from the outer edge (in pixels)" | |||||
| parser.add_argument("--minx", dest="minx", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--miny", dest="miny", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--maxx", dest="maxx", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--maxy", dest="maxy", metavar='INT', type=int, help=_help) | |||||
| _help = "Inset from each icons bounds (in pixels)" | |||||
| parser.add_argument("--minx_icon", dest="minx_icon", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--miny_icon", dest="miny_icon", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--maxx_icon", dest="maxx_icon", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--maxy_icon", dest="maxy_icon", metavar='INT', type=int, help=_help) | |||||
| _help = "Empty space between icons" | |||||
| parser.add_argument("--spacex_icon", dest="spacex_icon", metavar='INT', type=int, help=_help) | |||||
| parser.add_argument("--spacey_icon", dest="spacey_icon", metavar='INT', type=int, help=_help) | |||||
| del _help | |||||
| args = parser.parse_args(argv) | args = parser.parse_args(argv) | ||||
| if not argv: | if not argv: | ||||
| print("No args given!") | print("No args given!") | ||||
| parser.print_help() | parser.print_help() | ||||
| return | return | ||||
| dice(args.image, args.output, args.output_prefix, args.name_style, | main_ex(args.src, args.out, args.prefix, args.svg) | ||||
| args.parts_x, args.parts_y, | |||||
| args.minx, args.miny, args.maxx, args.maxy, | |||||
| args.minx_icon, args.miny_icon, args.maxx_icon, args.maxy_icon, | |||||
| args.spacex_icon, args.spacey_icon, | |||||
| ) | |||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||
| main() | main() | ||||