import collections, optparse, re, sys
formats_by_name = {}
formats_list = []
def read_color_h(filename):
"""
Read in the list of formats.
"""
formats = []
inside_enum = False
for line in open(filename):
line = line.strip()
if line == "{": continue
if line == "typedef enum ALLEGRO_PIXEL_FORMAT": inside_enum = True
elif inside_enum:
match = re.match(r"\s*ALLEGRO_PIXEL_FORMAT_(\w+)", line)
if match:
formats.append(match.group(1))
else:
break
return formats
def parse_format(format):
"""
Parse the format name into an info structure.
"""
if format.startswith("ANY"): return None
if "DXT" in format: return None
separator = format.find("_")
class Info: pass
class Component: pass
Info = collections.namedtuple("Info", "components, name, little_endian,"
"float, single_channel, size")
Component = collections.namedtuple("Component", "color, size,"
"position_from_left, position")
pos = 0
info = Info(
components={},
name=format,
little_endian="_LE" in format,
float=False,
single_channel=False,
size=None,
)
if "F32" in format:
return info._replace(
float=True,
size=128,
)
if "SINGLE_CHANNEL" in format:
return info._replace(
single_channel=True,
size=8,
)
for i in range(separator):
c = Component(
color=format[i],
size=int(format[separator + 1 + i]),
position_from_left=pos,
position=None,
)
info.components[c.color] = c
pos += c.size
size = pos
return info._replace(
components={k: c._replace(position=size - c.position_from_left - c.size)
for k, c in info.components.items()},
size=size,
float=False,
)
def macro_lines(info_a, info_b):
"""
Write out the lines of a conversion macro.
"""
r = ""
names = list(info_b.components.keys())
names.sort()
if info_a.float:
if info_b.single_channel:
return " (uint32_t)((x).r * 255)\n"
lines = []
for name in names:
if name == "X": continue
c = info_b.components[name]
mask = (1 << c.size) - 1
lines.append(
"((uint32_t)((x)." + name.lower() + " * " + str(mask) +
") << " + str(c.position) + ")")
r += " ("
r += " | \\\n ".join(lines)
r += ")\n"
return r
if info_b.float:
if info_a.single_channel:
return " al_map_rgb(x, 0, 0)\n"
lines = []
for name in "RGBA":
if name not in info_a.components: break
c = info_a.components[name]
mask = (1 << c.size) - 1
line = "((x) >> " + str(c.position) + ") & " + str(mask)
if c.size < 8:
line = "_al_rgb_scale_" + str(c.size) + "[" + line + "]"
lines.append(line)
r += " al_map_rgba(" if len(lines) == 4 else " al_map_rgb("
r += ",\\\n ".join(lines)
r += ")\n"
return r
if info_a.single_channel:
lines = []
for name in names:
if name in ["X", "G", "B"]:
continue
c = info_b.components[name]
shift = 8 - c.size - c.position
m = hex(((1 << c.size) - 1) << c.position)
if name == "A":
lines.append(m)
continue
if shift > 0:
lines.append("(((x) >> " + str(shift) + ") & " + m + ")")
elif shift < 0:
lines.append("(((x) << " + str(-shift) + ") & " + m + ")")
else:
lines.append("((x) & " + m + ")")
r += " ("
r += " | \\\n ".join(lines)
r += ")\n"
return r
if info_b.single_channel:
c = info_a.components["R"]
m = (1 << c.size) - 1
extract = "(((x) >> " + str(c.position) + ") & " + hex(m) + ")"
if (c.size != 8):
scale = "(_al_rgb_scale_" + str(c.size) + "[" + extract + "])"
else:
scale = extract
r += " " + scale + "\n"
return r
ops = {}
for name in names:
if name == "X": continue
c_b = info_b.components[name]
if name not in info_a.components:
if name == "A":
add = (1 << c_b.size) - 1
add <<= c_b.position
ops[name] = (0, 0, add, 0, 0, 0)
continue
c_a = info_a.components[name]
mask = (1 << c_b.size) - 1
shift_right = c_a.position
mask_pos = c_a.position
shift_left = c_b.position
bitdiff = c_a.size - c_b.size
if bitdiff > 0:
shift_right += bitdiff
mask_pos += bitdiff
else:
shift_left -= bitdiff
mask = (1 << c_a.size) - 1
mask <<= mask_pos
shift = shift_left - shift_right
ops[name] = (mask, shift, 0, c_a.size, c_b.size, mask_pos)
common_shifts = {}
for name, (mask, shift, add, size_a, size_b, mask_pos) in ops.items():
if not add and not (size_a != 8 and size_b == 8):
if shift in common_shifts: common_shifts[shift].append(name)
else: common_shifts[shift] = [name]
for newshift, colors in common_shifts.items():
if len(colors) == 1: continue
newname = ""
newmask = 0
colors.sort()
masks_pos = []
for name in colors:
mask, shift, add, size_a, size_b, mask_pos = ops[name]
names.remove(name)
newname += name
newmask |= mask
masks_pos.append(mask_pos)
names.append(newname)
ops[newname] = (newmask, newshift, 0, size_a, size_b, min(masks_pos))
lines = []
add_format = "0x%0" + str(info_b.size >> 2) + "x"
mask_format = "0x%0" + str(info_a.size >> 2) + "x"
for name in names:
if not name in ops: continue
mask, shift, add, size_a, size_b, mask_pos = ops[name]
if add:
line = "(" + (add_format % add) + ")"
lines.append((line, name, 0, size_a, size_b, mask_pos))
continue
mask_string = "((x) & " + (mask_format % mask) + ")"
if (size_a != 8 and size_b == 8):
line = "(_al_rgb_scale_" + str(size_a) + "[" + mask_string + " >> %2d" % mask_pos + "]"
else:
if (shift > 0):
line = "(" + mask_string + " << %2d" % shift + ")"
elif (shift < 0):
line = "(" + mask_string + " >> %2d" % -shift + ")"
else:
line = mask_string + " "
lines.append((line, name, shift, size_a, size_b, mask_pos))
for i in range(len(lines)):
line, name, shift, size_a, size_b, mask_pos = lines[i]
if i == 0: start = " ("
else: start = " "
if i == len(lines) - 1: cont = ") "
else: cont = " | \\"
if size_a != 8 and size_b == 8:
shift = shift+(mask_pos-(8-size_a))
if shift > 0:
backshift = " << %2d)" % shift
elif shift < 0:
backshift = " >> %2d)" % -shift
else:
backshift = " )"
else:
backshift = " "
r += start + line + backshift + " /* " + name + " */" + cont + "\n"
return r
def converter_macro(info_a, info_b):
"""
Create a conversion macro.
"""
if not info_a or not info_b: return None
name = "ALLEGRO_CONVERT_" + info_a.name + "_TO_" + info_b.name
r = ""
if info_a.little_endian or info_b.little_endian:
r += "#ifdef ALLEGRO_BIG_ENDIAN\n"
r += "#define " + name + "(x) \\\n"
if info_a.name == "ABGR_8888_LE":
r += macro_lines(formats_by_name["RGBA_8888"], info_b)
elif info_b.name == "ABGR_8888_LE":
r += macro_lines(info_a, formats_by_name["RGBA_8888"])
else:
r += "#error Conversion %s -> %s not understood by make_converters.py\n" % (
info_a.name, info_b.name)
r += "#else\n"
r += "#define " + name + "(x) \\\n"
r += macro_lines(info_a, info_b)
r += "#endif\n"
else:
r += "#define " + name + "(x) \\\n"
r += macro_lines(info_a, info_b)
return r
def write_convert_h(filename):
"""
Create the file with all the conversion macros.
"""
f = open(filename, "w")
f.write("""\
// Warning: This file was created by make_converters.py - do not edit.
#ifndef __al_included_allegro5_aintern_convert_h
#define __al_included_allegro5_aintern_convert_h
#include "allegro5/allegro.h"
#include "allegro5/internal/aintern_pixels.h"
""")
for a in formats_list:
for b in formats_list:
if b == a: continue
macro = converter_macro(a, b)
if macro:
f.write(macro)
f.write("""\
#endif
// Warning: This file was created by make_converters.py - do not edit.
""")
def converter_function(info_a, info_b):
"""
Create a string with one conversion function.
"""
name = info_a.name.lower() + "_to_" + info_b.name.lower()
params = "const void *src, int src_pitch,\n"
params += " void *dst, int dst_pitch,\n"
params += " int sx, int sy, int dx, int dy, int width, int height"
declaration = "static void " + name + "(" + params + ")"
macro_name = "ALLEGRO_CONVERT_" + info_a.name + "_TO_" + info_b.name
types_and_sizes = {
8 : ("uint8_t", "", 1),
15 : ("uint16_t", "", 2),
16 : ("uint16_t", "", 2),
24: ("uint8_t", " * 3", 1),
32 : ("uint32_t", "", 4),
128 : ("ALLEGRO_COLOR", "", 16)}
a_type, a_count, a_size = types_and_sizes[info_a.size]
b_type, b_count, b_size = types_and_sizes[info_b.size]
if a_count == "" and b_count == "":
conversion = """\
*dst_ptr = %(macro_name)s(*src_ptr);
dst_ptr++;
src_ptr++;""" % locals()
else:
if a_count != "":
s_conversion = """\
#ifdef ALLEGRO_BIG_ENDIAN
int src_pixel = src_ptr[2] | (src_ptr[1] << 8) | (src_ptr[0] << 16);
#else
int src_pixel = src_ptr[0] | (src_ptr[1] << 8) | (src_ptr[2] << 16);
#endif
""" % locals()
if b_count != "":
d_conversion = """\
#ifdef ALLEGRO_BIG_ENDIAN
dst_ptr[0] = dst_pixel >> 16;
dst_ptr[1] = dst_pixel >> 8;
dst_ptr[2] = dst_pixel;
#else
dst_ptr[0] = dst_pixel;
dst_ptr[1] = dst_pixel >> 8;
dst_ptr[2] = dst_pixel >> 16;
#endif
""" % locals()
if a_count != "" and b_count != "":
conversion = s_conversion + ("""\
int dst_pixel = %(macro_name)s(src_pixel);
""" % locals()) + d_conversion
elif a_count != "":
conversion = s_conversion + ("""\
*dst_ptr = %(macro_name)s(src_pixel);
""" % locals())
else:
conversion = ("""\
int dst_pixel = %(macro_name)s(*src_ptr);
""" % locals()) + d_conversion
conversion += """\
src_ptr += 1%(a_count)s;
dst_ptr += 1%(b_count)s;""" % locals()
r = declaration + "\n"
r += "{\n"
r += """\
int y;
const %(a_type)s *src_ptr = (const %(a_type)s *)((const char *)src + sy * src_pitch);
%(b_type)s *dst_ptr = (void *)((char *)dst + dy * dst_pitch);
int src_gap = src_pitch / %(a_size)d - width%(a_count)s;
int dst_gap = dst_pitch / %(b_size)d - width%(b_count)s;
src_ptr += sx%(a_count)s;
dst_ptr += dx%(b_count)s;
for (y = 0; y < height; y++) {
%(b_type)s *dst_end = dst_ptr + width%(b_count)s;
while (dst_ptr < dst_end) {
%(conversion)s
}
src_ptr += src_gap;
dst_ptr += dst_gap;
}
""" % locals()
r += "}\n"
return r
def write_convert_c(filename):
"""
Write out the file with the conversion functions.
"""
f = open(filename, "w")
f.write("""\
// Warning: This file was created by make_converters.py - do not edit.
#include "allegro5/allegro.h"
#include "allegro5/internal/aintern_bitmap.h"
#include "allegro5/internal/aintern_convert.h"
""")
for a in formats_list:
for b in formats_list:
if b == a: continue
if not a or not b: continue
function = converter_function(a, b)
f.write(function)
f.write("""\
void (*_al_convert_funcs[ALLEGRO_NUM_PIXEL_FORMATS]
[ALLEGRO_NUM_PIXEL_FORMATS])(const void *, int, void *, int,
int, int, int, int, int, int) = {
""")
for a in formats_list:
if not a:
f.write(" {NULL},\n")
else:
f.write(" {")
was_null = False
for b in formats_list:
if b and a != b:
name = a.name.lower() + "_to_" + b.name.lower()
f.write("\n " + name + ",")
was_null = False
else:
if not was_null: f.write("\n ")
f.write(" NULL,")
was_null = True
f.write("\n },\n")
f.write("""\
};
// Warning: This file was created by make_converters.py - do not edit.
""")
def main(argv):
global options
p = optparse.OptionParser()
p.description = """\
When run from the toplevel A5 folder, this will re-create the convert.h and
convert.c files containing all the low-level color conversion macros and
functions."""
options, args = p.parse_args()
formats = read_color_h("include/allegro5/color.h")
print(formats)
for f in formats:
info = parse_format(f)
formats_by_name[f] = info
formats_list.append(info)
write_convert_h("include/allegro5/internal/aintern_convert.h")
write_convert_c("src/convert.c")
if __name__ == "__main__":
main(sys.argv)