Fix T39597: Missing entries in VSE Preview menu
[blender.git] / source / blender / datatoc / datatoc_icon_split.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 This script dices up PNG into small files to store in version control.
23
24 Example:
25
26 ./blender.bin \
27     --background -noaudio \
28     --python ./release/datafiles/icon_dice.py -- \
29     --image=./release/datafiles/blender_icons16.png \
30     --output=./release/datafiles/blender_icons16
31     --output_prefix=icon16_
32     --name_style=UI_ICONS
33     --parts_x 26 --parts_y 32 \
34     --minx=10 --maxx 10 --miny 10 --maxy 10
35     --minx_icon 2 --maxx_icon 2 --miny_icon 2 --maxy_icon 2 \
36     --spacex_icon 1 --spacey_icon 1
37
38 """
39
40 import os
41
42 SOURCE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
43 VERBOSE = False
44
45
46 def image_from_file__bpy(filepath):
47     import bpy
48
49     image = bpy.data.images.load(filepath)
50     image.reload()
51
52     pixel_w, pixel_h = image.size
53     pixels = image.pixels[:]
54     return pixels, pixel_w, pixel_h
55
56
57 def image_from_file(filepath):
58     """
59     Return pixels, w, h from an image.
60
61     note: bpy import is ONLY used here.
62     """
63
64     try:
65         import bpy
66     except:
67         bpy = None
68
69     if bpy is not None:
70         pixels, pixel_w, pixel_h = image_from_file__bpy(filepath)
71     #else:
72     #    pixels, pixel_w, pixel_h = image_from_file__py(filepath)
73
74     return pixels, pixel_w, pixel_h
75
76
77 def write_subimage(sub_x, sub_y, sub_w, sub_h,
78                    filepath,
79                    pixels, pixel_w, pixel_h):
80     import struct
81
82     # first check if the icon is worth writing
83     is_fill = False
84     for y in range(sub_h):
85         for x in range(sub_w):
86             i = (sub_x + x) + ((sub_y + y) * pixel_w)
87             a = pixels[(i * 4) + 3]
88             if a != 0.0:
89                 is_fill = True
90                 break
91
92     if not is_fill:
93         # print("skipping:", filepath)
94         return
95
96     with open(filepath, 'wb') as f:
97
98         f.write(struct.pack('<6I',
99                 sub_w, sub_h,
100                 sub_x, sub_y,
101                 # redundant but including to maintain consistency
102                 pixel_w, pixel_h,
103                 ))
104
105         for y in range(sub_h):
106             for x in range(sub_w):
107                 i = (sub_x + x) + ((sub_y + y) * pixel_w)
108                 rgba = pixels[(i * 4):(i * 4) + 4]
109                 c = sum((int(p * 255) << (8 * i)) for i, p in enumerate(rgba))
110                 f.write(struct.pack("<I", c))
111
112
113 _dice_icon_name_cache = {}
114
115
116 def dice_icon_name(x, y, parts_x, parts_y,
117                    name_style=None, prefix=""):
118     """
119     How to name icons, this is mainly for what name we get in git,
120     the actual names don't really matter, its just nice to have the
121     name match up with something recognizable for commits.
122     """
123     if name_style == 'UI_ICONS':
124
125         # Init on demand
126         if not _dice_icon_name_cache:
127             import re
128
129             # Search for eg: DEF_ICON(BRUSH_NUDGE) --> BRUSH_NUDGE
130             re_icon = re.compile('^\s*DEF_ICON\(\s*([A-Za-z0-9_]+)\s*\).*$')
131
132             ui_icons_h = os.path.join(SOURCE_DIR, "source", "blender", "editors", "include", "UI_icons.h")
133             with open(ui_icons_h, 'r', encoding="utf-8") as f:
134                 for l in f:
135                     match = re_icon.search(l)
136                     if match:
137                         icon_name = match.group(1).lower()
138                         # print(l.rstrip())
139                         _dice_icon_name_cache[len(_dice_icon_name_cache)] = icon_name
140         # ---- Done with icon cache
141
142         index = (y * parts_x) + x
143         icon_name = _dice_icon_name_cache[index]
144
145         # for debugging its handy to sort by number
146         #~ id_str = "%03d_%s%s.dat" % (index, prefix, icon_name)
147
148         id_str = "%s%s.dat" % (prefix, icon_name)
149
150     elif name_style == "":
151         # flip so icons are numbered from top-left
152         # because new icons will be added at the bottom
153         y_flip = parts_y - (y + 1)
154         id_str = "%s%02xx%02x.dat" % (prefix, x, y_flip)
155     else:
156         raise Exception("Invalid '--name_style' arg")
157
158     return id_str
159
160
161 def dice(filepath, output, output_prefix, name_style,
162          parts_x, parts_y,
163          minx, miny, maxx, maxy,
164          minx_icon, miny_icon, maxx_icon, maxy_icon,
165          spacex_icon, spacey_icon,
166          ):
167
168     is_simple = (max(minx, miny, maxx, maxy,
169                      minx_icon, miny_icon, maxx_icon, maxy_icon,
170                      spacex_icon, spacey_icon) == 0)
171
172     pixels, pixel_w, pixel_h = image_from_file(filepath)
173
174     if not (pixel_w and pixel_h):
175         print("Image not found %r!" % filepath)
176         return
177
178     if not os.path.exists(output):
179         os.mkdir(output)
180
181     if is_simple:
182         pixels_w_clip = pixel_w
183         pixels_h_clip = pixel_h
184
185         icon_w = pixels_w_clip // parts_x
186         icon_h = pixels_h_clip // parts_y
187         icon_w_clip = icon_w
188         icon_h_clip = icon_h
189     else:
190         pixels_w_clip = pixel_w - (minx + maxx)
191         pixels_h_clip = pixel_h - (miny + maxy)
192
193         icon_w = (pixels_w_clip - ((parts_x - 1) * spacex_icon)) // parts_x
194         icon_h = (pixels_h_clip - ((parts_y - 1) * spacey_icon)) // parts_y
195         icon_w_clip = icon_w - (minx_icon + maxx_icon)
196         icon_h_clip = icon_h - (miny_icon + maxy_icon)
197
198     print(pixel_w, pixel_h, icon_w, icon_h)
199
200     for x in range(parts_x):
201         for y in range(parts_y):
202             id_str = dice_icon_name(x, y,
203                                     parts_x, parts_y,
204                                     name_style=name_style, prefix=output_prefix)
205             filepath = os.path.join(output, id_str)
206             if VERBOSE:
207                 print("  writing:", filepath)
208
209             # simple, no margins
210             if is_simple:
211                 sub_x = x * icon_w
212                 sub_y = y * icon_h
213             else:
214                 sub_x = minx + ((x * (icon_w + spacex_icon)) + minx_icon)
215                 sub_y = miny + ((y * (icon_h + spacey_icon)) + miny_icon)
216
217             write_subimage(sub_x, sub_y, icon_w_clip, icon_h_clip,
218                            filepath,
219                            pixels, pixel_w, pixel_h)
220
221
222 def main():
223     import sys
224     import argparse
225
226     epilog = "Run this after updating the SVG file"
227
228     argv = sys.argv
229
230     if "--" not in argv:
231         argv = []
232     else:
233         argv = argv[argv.index("--") + 1:]
234
235     parser = argparse.ArgumentParser(description=__doc__, epilog=epilog)
236
237     # File path options
238     parser.add_argument("--image", dest="image", metavar='FILE',
239             help="Image file")
240
241     parser.add_argument("--output", dest="output", metavar='DIR',
242             help="Output directory")
243
244     parser.add_argument("--output_prefix", dest="output_prefix", metavar='STRING',
245             help="Output prefix")
246
247     # Icon naming option
248     parser.add_argument("--name_style", dest="name_style", metavar='ENUM', type=str,
249             choices=('', 'UI_ICONS'),
250             help="The metod used for naming output data")
251
252     # Options for dicing up the image
253     parser.add_argument("--parts_x", dest="parts_x", metavar='INT', type=int,
254             help="Grid X parts")
255     parser.add_argument("--parts_y", dest="parts_y", metavar='INT', type=int,
256             help="Grid Y parts")
257
258     _help = "Inset from the outer edge (in pixels)"
259     parser.add_argument("--minx", dest="minx", metavar='INT', type=int, help=_help)
260     parser.add_argument("--miny", dest="miny", metavar='INT', type=int, help=_help)
261     parser.add_argument("--maxx", dest="maxx", metavar='INT', type=int, help=_help)
262     parser.add_argument("--maxy", dest="maxy", metavar='INT', type=int, help=_help)
263
264     _help = "Inset from each icons bounds (in pixels)"
265     parser.add_argument("--minx_icon", dest="minx_icon", metavar='INT', type=int, help=_help)
266     parser.add_argument("--miny_icon", dest="miny_icon", metavar='INT', type=int, help=_help)
267     parser.add_argument("--maxx_icon", dest="maxx_icon", metavar='INT', type=int, help=_help)
268     parser.add_argument("--maxy_icon", dest="maxy_icon", metavar='INT', type=int, help=_help)
269
270     _help = "Empty space between icons"
271     parser.add_argument("--spacex_icon", dest="spacex_icon", metavar='INT', type=int, help=_help)
272     parser.add_argument("--spacey_icon", dest="spacey_icon", metavar='INT', type=int, help=_help)
273
274     del _help
275
276     args = parser.parse_args(argv)
277
278     if not argv:
279         print("No args given!")
280         parser.print_help()
281         return
282
283     dice(args.image, args.output, args.output_prefix, args.name_style,
284          args.parts_x, args.parts_y,
285          args.minx, args.miny, args.maxx, args.maxy,
286          args.minx_icon, args.miny_icon, args.maxx_icon, args.maxy_icon,
287          args.spacex_icon, args.spacey_icon,
288          )
289
290 if __name__ == "__main__":
291     main()