fix T66899: Collada: Shininess/Reflectivity not handled correct
[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 ImportError:
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(
99             struct.pack(
100                 '<6I',
101                 sub_w, sub_h,
102                 sub_x, sub_y,
103                 # redundant but including to maintain consistency
104                 pixel_w, pixel_h,
105             ))
106
107         for y in range(sub_h):
108             for x in range(sub_w):
109                 i = (sub_x + x) + ((sub_y + y) * pixel_w)
110                 rgba = pixels[(i * 4):(i * 4) + 4]
111                 c = sum((int(p * 255) << (8 * i)) for i, p in enumerate(rgba))
112                 f.write(struct.pack("<I", c))
113
114
115 _dice_icon_name_cache = {}
116
117
118 def dice_icon_name(
119         x, y, parts_x, parts_y,
120         name_style=None, prefix=""):
121     """
122     How to name icons, this is mainly for what name we get in git,
123     the actual names don't really matter, its just nice to have the
124     name match up with something recognizable for commits.
125     """
126     if name_style == 'UI_ICONS':
127
128         # Init on demand
129         if not _dice_icon_name_cache:
130             import re
131             count = 0
132
133             # Search for eg: DEF_ICON(BRUSH_NUDGE) --> BRUSH_NUDGE
134             re_icon = re.compile(r'^\s*DEF_ICON.*\(\s*([A-Za-z0-9_]+)\s*\).*$')
135
136             ui_icons_h = os.path.join(SOURCE_DIR, "source", "blender", "editors", "include", "UI_icons.h")
137             with open(ui_icons_h, 'r', encoding="utf-8") as f:
138                 for l in f:
139                     match = re_icon.search(l)
140                     if match:
141                         if l.find('DEF_ICON_BLANK') == -1:
142                             icon_name = match.group(1).lower()
143                             print(icon_name)
144                             _dice_icon_name_cache[count] = icon_name
145                         count += 1
146         # ---- Done with icon cache
147
148         index = (y * parts_x) + x
149         if index not in _dice_icon_name_cache:
150             return None
151
152         icon_name = _dice_icon_name_cache[index]
153
154         # for debugging its handy to sort by number
155         # ~ id_str = "%03d_%s%s.dat" % (index, prefix, icon_name)
156
157         id_str = "%s%s.dat" % (prefix, icon_name)
158
159     elif name_style == "":
160         # flip so icons are numbered from top-left
161         # because new icons will be added at the bottom
162         y_flip = parts_y - (y + 1)
163         id_str = "%s%02xx%02x.dat" % (prefix, x, y_flip)
164     else:
165         raise Exception("Invalid '--name_style' arg")
166
167     return id_str
168
169
170 def dice(
171         filepath, output, output_prefix, name_style,
172         parts_x, parts_y,
173         minx, miny, maxx, maxy,
174         minx_icon, miny_icon, maxx_icon, maxy_icon,
175         spacex_icon, spacey_icon,
176 ):
177
178     is_simple = (max(
179         minx, miny, maxx, maxy,
180         minx_icon, miny_icon, maxx_icon, maxy_icon,
181         spacex_icon, spacey_icon) == 0)
182
183     pixels, pixel_w, pixel_h = image_from_file(filepath)
184
185     if not (pixel_w and pixel_h):
186         print("Image not found %r!" % filepath)
187         return
188
189     if not os.path.exists(output):
190         os.mkdir(output)
191
192     if is_simple:
193         pixels_w_clip = pixel_w
194         pixels_h_clip = pixel_h
195
196         icon_w = pixels_w_clip // parts_x
197         icon_h = pixels_h_clip // parts_y
198         icon_w_clip = icon_w
199         icon_h_clip = icon_h
200     else:
201         pixels_w_clip = pixel_w - (minx + maxx)
202         pixels_h_clip = pixel_h - (miny + maxy)
203
204         icon_w = (pixels_w_clip - ((parts_x - 1) * spacex_icon)) // parts_x
205         icon_h = (pixels_h_clip - ((parts_y - 1) * spacey_icon)) // parts_y
206         icon_w_clip = icon_w - (minx_icon + maxx_icon)
207         icon_h_clip = icon_h - (miny_icon + maxy_icon)
208
209     print(pixel_w, pixel_h, icon_w, icon_h)
210
211     for x in range(parts_x):
212         for y in range(parts_y):
213             id_str = dice_icon_name(
214                 x, y,
215                 parts_x, parts_y,
216                 name_style=name_style, prefix=output_prefix
217             )
218             if not id_str:
219                 continue
220
221             filepath = os.path.join(output, id_str)
222             if VERBOSE:
223                 print("  writing:", filepath)
224
225             # simple, no margins
226             if is_simple:
227                 sub_x = x * icon_w
228                 sub_y = y * icon_h
229             else:
230                 sub_x = minx + ((x * (icon_w + spacex_icon)) + minx_icon)
231                 sub_y = miny + ((y * (icon_h + spacey_icon)) + miny_icon)
232
233             write_subimage(sub_x, sub_y, icon_w_clip, icon_h_clip,
234                            filepath,
235                            pixels, pixel_w, pixel_h)
236
237
238 def main():
239     import sys
240     import argparse
241
242     epilog = "Run this after updating the SVG file"
243
244     argv = sys.argv
245
246     if "--" not in argv:
247         argv = []
248     else:
249         argv = argv[argv.index("--") + 1:]
250
251     parser = argparse.ArgumentParser(description=__doc__, epilog=epilog)
252
253     # File path options
254     parser.add_argument(
255         "--image", dest="image", metavar='FILE',
256         help="Image file",
257     )
258     parser.add_argument(
259         "--output", dest="output", metavar='DIR',
260         help="Output directory",
261     )
262     parser.add_argument(
263         "--output_prefix", dest="output_prefix", metavar='STRING',
264         help="Output prefix",
265     )
266
267     # Icon naming option
268     parser.add_argument(
269         "--name_style", dest="name_style", metavar='ENUM', type=str,
270         choices=('', 'UI_ICONS'),
271         help="The metod used for naming output data",
272     )
273
274     # Options for dicing up the image
275     parser.add_argument(
276         "--parts_x", dest="parts_x", metavar='INT', type=int,
277         help="Grid X parts",
278     )
279     parser.add_argument(
280         "--parts_y", dest="parts_y", metavar='INT', type=int,
281         help="Grid Y parts",
282     )
283
284     _help = "Inset from the outer edge (in pixels)"
285     parser.add_argument("--minx", dest="minx", metavar='INT', type=int, help=_help)
286     parser.add_argument("--miny", dest="miny", metavar='INT', type=int, help=_help)
287     parser.add_argument("--maxx", dest="maxx", metavar='INT', type=int, help=_help)
288     parser.add_argument("--maxy", dest="maxy", metavar='INT', type=int, help=_help)
289
290     _help = "Inset from each icons bounds (in pixels)"
291     parser.add_argument("--minx_icon", dest="minx_icon", metavar='INT', type=int, help=_help)
292     parser.add_argument("--miny_icon", dest="miny_icon", metavar='INT', type=int, help=_help)
293     parser.add_argument("--maxx_icon", dest="maxx_icon", metavar='INT', type=int, help=_help)
294     parser.add_argument("--maxy_icon", dest="maxy_icon", metavar='INT', type=int, help=_help)
295
296     _help = "Empty space between icons"
297     parser.add_argument("--spacex_icon", dest="spacex_icon", metavar='INT', type=int, help=_help)
298     parser.add_argument("--spacey_icon", dest="spacey_icon", metavar='INT', type=int, help=_help)
299
300     del _help
301
302     args = parser.parse_args(argv)
303
304     if not argv:
305         print("No args given!")
306         parser.print_help()
307         return
308
309     dice(args.image, args.output, args.output_prefix, args.name_style,
310          args.parts_x, args.parts_y,
311          args.minx, args.miny, args.maxx, args.maxy,
312          args.minx_icon, args.miny_icon, args.maxx_icon, args.maxy_icon,
313          args.spacex_icon, args.spacey_icon,
314          )
315
316
317 if __name__ == "__main__":
318     main()