1#!/usr/bin/env python3 2import collections, optparse, re, sys 3 4formats_by_name = {} 5formats_list = [] 6 7def read_color_h(filename): 8 """ 9 Read in the list of formats. 10 """ 11 formats = [] 12 inside_enum = False 13 for line in open(filename): 14 line = line.strip() 15 if line == "{": continue 16 if line == "typedef enum ALLEGRO_PIXEL_FORMAT": inside_enum = True 17 elif inside_enum: 18 match = re.match(r"\s*ALLEGRO_PIXEL_FORMAT_(\w+)", line) 19 if match: 20 formats.append(match.group(1)) 21 else: 22 break 23 return formats 24 25def parse_format(format): 26 """ 27 Parse the format name into an info structure. 28 """ 29 if format.startswith("ANY"): return None 30 if "DXT" in format: return None 31 32 separator = format.find("_") 33 class Info: pass 34 class Component: pass 35 36 Info = collections.namedtuple("Info", "components, name, little_endian," 37 "float, single_channel, size") 38 Component = collections.namedtuple("Component", "color, size," 39 "position_from_left, position") 40 41 pos = 0 42 info = Info( 43 components={}, 44 name=format, 45 little_endian="_LE" in format, 46 float=False, 47 single_channel=False, 48 size=None, 49 ) 50 51 if "F32" in format: 52 return info._replace( 53 float=True, 54 size=128, 55 ) 56 57 if "SINGLE_CHANNEL" in format: 58 return info._replace( 59 single_channel=True, 60 size=8, 61 ) 62 63 for i in range(separator): 64 c = Component( 65 color=format[i], 66 size=int(format[separator + 1 + i]), 67 position_from_left=pos, 68 position=None, 69 ) 70 info.components[c.color] = c 71 pos += c.size 72 size = pos 73 return info._replace( 74 components={k: c._replace(position=size - c.position_from_left - c.size) 75 for k, c in info.components.items()}, 76 size=size, 77 float=False, 78 ) 79 80def macro_lines(info_a, info_b): 81 """ 82 Write out the lines of a conversion macro. 83 """ 84 r = "" 85 86 names = list(info_b.components.keys()) 87 names.sort() 88 89 if info_a.float: 90 if info_b.single_channel: 91 return " (uint32_t)((x).r * 255)\n" 92 lines = [] 93 for name in names: 94 if name == "X": continue 95 c = info_b.components[name] 96 mask = (1 << c.size) - 1 97 lines.append( 98 "((uint32_t)((x)." + name.lower() + " * " + str(mask) + 99 ") << " + str(c.position) + ")") 100 r += " (" 101 r += " | \\\n ".join(lines) 102 r += ")\n" 103 return r 104 105 if info_b.float: 106 if info_a.single_channel: 107 return " al_map_rgb(x, 0, 0)\n" 108 lines = [] 109 for name in "RGBA": 110 if name not in info_a.components: break 111 c = info_a.components[name] 112 mask = (1 << c.size) - 1 113 line = "((x) >> " + str(c.position) + ") & " + str(mask) 114 if c.size < 8: 115 line = "_al_rgb_scale_" + str(c.size) + "[" + line + "]" 116 lines.append(line) 117 118 r += " al_map_rgba(" if len(lines) == 4 else " al_map_rgb(" 119 r += ",\\\n ".join(lines) 120 r += ")\n" 121 return r 122 123 if info_a.single_channel: 124 lines = [] 125 for name in names: 126 # Only map to R, and let A=1 127 if name in ["X", "G", "B"]: 128 continue 129 c = info_b.components[name] 130 shift = 8 - c.size - c.position 131 m = hex(((1 << c.size) - 1) << c.position) 132 if name == "A": 133 lines.append(m) 134 continue 135 if shift > 0: 136 lines.append("(((x) >> " + str(shift) + ") & " + m + ")") 137 elif shift < 0: 138 lines.append("(((x) << " + str(-shift) + ") & " + m + ")") 139 else: 140 lines.append("((x) & " + m + ")") 141 r += " (" 142 r += " | \\\n ".join(lines) 143 r += ")\n" 144 return r 145 146 if info_b.single_channel: 147 c = info_a.components["R"] 148 m = (1 << c.size) - 1 149 extract = "(((x) >> " + str(c.position) + ") & " + hex(m) + ")" 150 if (c.size != 8): 151 scale = "(_al_rgb_scale_" + str(c.size) + "[" + extract + "])" 152 else: 153 scale = extract 154 r += " " + scale + "\n" 155 return r 156 157 # Generate a list of (mask, shift, add) tuples for all components. 158 ops = {} 159 for name in names: 160 if name == "X": continue # We simply ignore X components. 161 c_b = info_b.components[name] 162 if name not in info_a.components: 163 # Set A component to all 1 bits if the source doesn't have it. 164 if name == "A": 165 add = (1 << c_b.size) - 1 166 add <<= c_b.position 167 ops[name] = (0, 0, add, 0, 0, 0) 168 continue 169 c_a = info_a.components[name] 170 mask = (1 << c_b.size) - 1 171 shift_right = c_a.position 172 mask_pos = c_a.position 173 shift_left = c_b.position 174 bitdiff = c_a.size - c_b.size 175 if bitdiff > 0: 176 shift_right += bitdiff 177 mask_pos += bitdiff 178 else: 179 shift_left -= bitdiff 180 mask = (1 << c_a.size) - 1 181 182 mask <<= mask_pos 183 shift = shift_left - shift_right 184 ops[name] = (mask, shift, 0, c_a.size, c_b.size, mask_pos) 185 186 # Collapse multiple components if possible. 187 common_shifts = {} 188 for name, (mask, shift, add, size_a, size_b, mask_pos) in ops.items(): 189 if not add and not (size_a != 8 and size_b == 8): 190 if shift in common_shifts: common_shifts[shift].append(name) 191 else: common_shifts[shift] = [name] 192 for newshift, colors in common_shifts.items(): 193 if len(colors) == 1: continue 194 newname = "" 195 newmask = 0 196 colors.sort() 197 masks_pos = [] 198 for name in colors: 199 mask, shift, add, size_a, size_b, mask_pos = ops[name] 200 201 names.remove(name) 202 newname += name 203 newmask |= mask 204 masks_pos.append(mask_pos) 205 names.append(newname) 206 ops[newname] = (newmask, newshift, 0, size_a, size_b, min(masks_pos)) 207 208 # Write out a line for each remaining operation. 209 lines = [] 210 add_format = "0x%0" + str(info_b.size >> 2) + "x" 211 mask_format = "0x%0" + str(info_a.size >> 2) + "x" 212 for name in names: 213 if not name in ops: continue 214 mask, shift, add, size_a, size_b, mask_pos = ops[name] 215 if add: 216 line = "(" + (add_format % add) + ")" 217 lines.append((line, name, 0, size_a, size_b, mask_pos)) 218 continue 219 mask_string = "((x) & " + (mask_format % mask) + ")" 220 if (size_a != 8 and size_b == 8): 221 line = "(_al_rgb_scale_" + str(size_a) + "[" + mask_string + " >> %2d" % mask_pos + "]" 222 else: 223 if (shift > 0): 224 line = "(" + mask_string + " << %2d" % shift + ")" 225 elif (shift < 0): 226 line = "(" + mask_string + " >> %2d" % -shift + ")" 227 else: 228 line = mask_string + " " 229 lines.append((line, name, shift, size_a, size_b, mask_pos)) 230 231 # Concoct the macro. 232 for i in range(len(lines)): 233 line, name, shift, size_a, size_b, mask_pos = lines[i] 234 if i == 0: start = " (" 235 else: start = " " 236 if i == len(lines) - 1: cont = ") " 237 else: cont = " | \\" 238 if size_a != 8 and size_b == 8: 239 shift = shift+(mask_pos-(8-size_a)) 240 if shift > 0: 241 backshift = " << %2d)" % shift 242 elif shift < 0: 243 backshift = " >> %2d)" % -shift 244 else: 245 backshift = " )" 246 else: 247 backshift = " " 248 r += start + line + backshift + " /* " + name + " */" + cont + "\n" 249 return r 250 251 252def converter_macro(info_a, info_b): 253 """ 254 Create a conversion macro. 255 """ 256 if not info_a or not info_b: return None 257 258 name = "ALLEGRO_CONVERT_" + info_a.name + "_TO_" + info_b.name 259 260 r = "" 261 262 if info_a.little_endian or info_b.little_endian: 263 r += "#ifdef ALLEGRO_BIG_ENDIAN\n" 264 r += "#define " + name + "(x) \\\n" 265 if info_a.name == "ABGR_8888_LE": 266 r += macro_lines(formats_by_name["RGBA_8888"], info_b) 267 elif info_b.name == "ABGR_8888_LE": 268 r += macro_lines(info_a, formats_by_name["RGBA_8888"]) 269 else: 270 r += "#error Conversion %s -> %s not understood by make_converters.py\n" % ( 271 info_a.name, info_b.name) 272 r += "#else\n" 273 r += "#define " + name + "(x) \\\n" 274 r += macro_lines(info_a, info_b) 275 r += "#endif\n" 276 else: 277 r += "#define " + name + "(x) \\\n" 278 r += macro_lines(info_a, info_b) 279 280 return r 281 282def write_convert_h(filename): 283 """ 284 Create the file with all the conversion macros. 285 """ 286 f = open(filename, "w") 287 f.write("""\ 288// Warning: This file was created by make_converters.py - do not edit. 289#ifndef __al_included_allegro5_aintern_convert_h 290#define __al_included_allegro5_aintern_convert_h 291 292#include "allegro5/allegro.h" 293#include "allegro5/internal/aintern_pixels.h" 294""") 295 296 for a in formats_list: 297 for b in formats_list: 298 if b == a: continue 299 macro = converter_macro(a, b) 300 if macro: 301 f.write(macro) 302 303 f.write("""\ 304#endif 305// Warning: This file was created by make_converters.py - do not edit. 306""") 307 308def converter_function(info_a, info_b): 309 """ 310 Create a string with one conversion function. 311 """ 312 name = info_a.name.lower() + "_to_" + info_b.name.lower() 313 params = "const void *src, int src_pitch,\n" 314 params += " void *dst, int dst_pitch,\n" 315 params += " int sx, int sy, int dx, int dy, int width, int height" 316 declaration = "static void " + name + "(" + params + ")" 317 318 macro_name = "ALLEGRO_CONVERT_" + info_a.name + "_TO_" + info_b.name 319 320 types_and_sizes = { 321 8 : ("uint8_t", "", 1), 322 15 : ("uint16_t", "", 2), 323 16 : ("uint16_t", "", 2), 324 24: ("uint8_t", " * 3", 1), 325 32 : ("uint32_t", "", 4), 326 128 : ("ALLEGRO_COLOR", "", 16)} 327 a_type, a_count, a_size = types_and_sizes[info_a.size] 328 b_type, b_count, b_size = types_and_sizes[info_b.size] 329 330 if a_count == "" and b_count == "": 331 conversion = """\ 332 *dst_ptr = %(macro_name)s(*src_ptr); 333 dst_ptr++; 334 src_ptr++;""" % locals() 335 else: 336 337 if a_count != "": 338 s_conversion = """\ 339 #ifdef ALLEGRO_BIG_ENDIAN 340 int src_pixel = src_ptr[2] | (src_ptr[1] << 8) | (src_ptr[0] << 16); 341 #else 342 int src_pixel = src_ptr[0] | (src_ptr[1] << 8) | (src_ptr[2] << 16); 343 #endif 344""" % locals() 345 346 if b_count != "": 347 d_conversion = """\ 348 #ifdef ALLEGRO_BIG_ENDIAN 349 dst_ptr[0] = dst_pixel >> 16; 350 dst_ptr[1] = dst_pixel >> 8; 351 dst_ptr[2] = dst_pixel; 352 #else 353 dst_ptr[0] = dst_pixel; 354 dst_ptr[1] = dst_pixel >> 8; 355 dst_ptr[2] = dst_pixel >> 16; 356 #endif 357""" % locals() 358 359 if a_count != "" and b_count != "": 360 conversion = s_conversion + ("""\ 361 int dst_pixel = %(macro_name)s(src_pixel); 362""" % locals()) + d_conversion 363 364 elif a_count != "": 365 conversion = s_conversion + ("""\ 366 *dst_ptr = %(macro_name)s(src_pixel); 367""" % locals()) 368 369 else: 370 conversion = ("""\ 371 int dst_pixel = %(macro_name)s(*src_ptr); 372""" % locals()) + d_conversion 373 374 conversion += """\ 375 src_ptr += 1%(a_count)s; 376 dst_ptr += 1%(b_count)s;""" % locals() 377 378 r = declaration + "\n" 379 r += "{\n" 380 r += """\ 381 int y; 382 const %(a_type)s *src_ptr = (const %(a_type)s *)((const char *)src + sy * src_pitch); 383 %(b_type)s *dst_ptr = (void *)((char *)dst + dy * dst_pitch); 384 int src_gap = src_pitch / %(a_size)d - width%(a_count)s; 385 int dst_gap = dst_pitch / %(b_size)d - width%(b_count)s; 386 src_ptr += sx%(a_count)s; 387 dst_ptr += dx%(b_count)s; 388 for (y = 0; y < height; y++) { 389 %(b_type)s *dst_end = dst_ptr + width%(b_count)s; 390 while (dst_ptr < dst_end) { 391%(conversion)s 392 } 393 src_ptr += src_gap; 394 dst_ptr += dst_gap; 395 } 396""" % locals() 397 398 r += "}\n" 399 400 return r 401 402def write_convert_c(filename): 403 """ 404 Write out the file with the conversion functions. 405 """ 406 f = open(filename, "w") 407 f.write("""\ 408// Warning: This file was created by make_converters.py - do not edit. 409#include "allegro5/allegro.h" 410#include "allegro5/internal/aintern_bitmap.h" 411#include "allegro5/internal/aintern_convert.h" 412""") 413 414 for a in formats_list: 415 for b in formats_list: 416 if b == a: continue 417 if not a or not b: continue 418 function = converter_function(a, b) 419 f.write(function) 420 421 f.write("""\ 422void (*_al_convert_funcs[ALLEGRO_NUM_PIXEL_FORMATS] 423 [ALLEGRO_NUM_PIXEL_FORMATS])(const void *, int, void *, int, 424 int, int, int, int, int, int) = { 425""") 426 for a in formats_list: 427 if not a: 428 f.write(" {NULL},\n") 429 else: 430 f.write(" {") 431 was_null = False 432 for b in formats_list: 433 if b and a != b: 434 name = a.name.lower() + "_to_" + b.name.lower() 435 f.write("\n " + name + ",") 436 was_null = False 437 else: 438 if not was_null: f.write("\n ") 439 f.write(" NULL,") 440 was_null = True 441 f.write("\n },\n") 442 443 f.write("""\ 444}; 445 446// Warning: This file was created by make_converters.py - do not edit. 447""") 448 449def main(argv): 450 global options 451 p = optparse.OptionParser() 452 p.description = """\ 453When run from the toplevel A5 folder, this will re-create the convert.h and 454convert.c files containing all the low-level color conversion macros and 455functions.""" 456 options, args = p.parse_args() 457 458 # Read in color.h to get the available formats. 459 formats = read_color_h("include/allegro5/color.h") 460 461 print(formats) 462 463 # Parse the component info for each format. 464 for f in formats: 465 info = parse_format(f) 466 formats_by_name[f] = info 467 formats_list.append(info) 468 469 # Output a macro for each possible conversion. 470 write_convert_h("include/allegro5/internal/aintern_convert.h") 471 472 # Output a function for each possible conversion. 473 write_convert_c("src/convert.c") 474 475if __name__ == "__main__": 476 main(sys.argv) 477 478# vim: set sts=4 sw=4 et: 479