diff --git a/fxconv/fxconv-main.py b/fxconv/fxconv-main.py index 931cbae..db7efef 100755 --- a/fxconv/fxconv-main.py +++ b/fxconv/fxconv-main.py @@ -15,17 +15,18 @@ fxconv converts data files such as images and fonts into gint formats optimized for fast execution, or into object files. Operating modes: - -s, --script Expose the fxconv module and run this Python script - -b, --binary Turn data into an object file, no conversion - -i, --image Convert to gint's image format - -f, --font Convert to gint's font format + -s, --script Expose the fxconv module and run this Python script + -b, --binary Turn data into an object file, no conversion + -i, --image Convert to gint's bopti image format + -f, --font Convert to gint's topti font format + --libimg-image Convert to the libimg image format When using -s, additional arguments are stored in the [fxconv.args] variable of the module. This is intended to be a restricted list of file names specified by a Makefile, used to convert only a subset of the files in the script. -The -b, -i and -f modes are shortcuts to convert single files without a script. -They accept parameters with a "category.key:value" syntax, for example: +The operating mode options are shortcuts to convert single files without a +script. They accept parameters with a "category.key:value" syntax, for example: fxconv -f myfont.png -o myfont.o charset:ascii grid.padding:1 height:7 When converting images, use --fx (black-and-white calculators) or --cg (16-bit @@ -36,11 +37,11 @@ color calculators) to specify the target machine. def err(msg): print("\x1b[31;1merror:\x1b[0m", msg, file=sys.stderr) def warn(msg): - print("warning:", msg, file=sys.stderr) + print("\x1b[33;1mwarning:\x1b[0m", msg, file=sys.stderr) def main(): # Default execution mode is to run a Python script for conversion - modes = "script binary image font" + modes = "script binary image font bopti-image libimg-image" mode = "s" output = None model = None @@ -79,7 +80,7 @@ def main(): target['section'] = value # Other names are modes else: - mode = name[1] if len(name)==2 else name[2] + mode = name[1] if len(name)==2 else name[2:] # Remaining arguments if args == []: @@ -118,7 +119,17 @@ def main(): for (name, value) in args: insert(params, name.split("."), value) - params["type"] = { "b": "binary", "i": "image", "f": "font" }[mode] + if "type" in params: + pass + elif(len(mode) == 1): + params["type"] = { "b": "binary", "i": "image", "f": "font" }[mode] + else: + params["type"] = mode + + # Will be deprecated in the future + if params["type"] == "image": + warn("type 'image' is deprecated, use 'bopti-image' instead") + params["type"] = "bopti-image" try: fxconv.convert(input, params, target, output, model) diff --git a/fxconv/fxconv.py b/fxconv/fxconv.py index d8d6ab9..f2c7d5c 100644 --- a/fxconv/fxconv.py +++ b/fxconv/fxconv.py @@ -503,6 +503,42 @@ def convert_topti(input, output, params, target): elf(data, output, "_" + params["name"], **target) +# +# libimg conversion for fx-CG 50 +# + +def convert_libimg_cg(input, output, params, target): + img = Image.open(input) + if img.width >= 65536 or img.height >= 65536: + raise FxconvError(f"'{input}' is too large (max. 65535x65535)") + + # Crop image to key "area" + area = Area(params.get("area", {}), img) + img = img.crop(area.tuple()) + + # Encode the image into 16-bit format and force the alpha to 0x0001 + encoded, alpha = r5g6b5(img, alpha=(0x0001,0x0000)) + + w, h, s = img.width, img.height, img.width + FLAG_OWN = 1 + FLAG_RO = 2 + + assembly = f""" + .section .rodata + .global _{params["name"]} + + _{params["name"]}: + .word {img.width} + .word {img.height} + .word {img.width} + .byte {FLAG_RO} + .byte 0 + .long _{params["name"]}_data + """ + + dataname = "_{}_data".format(params["name"]) + elf(encoded, output, dataname, assembly=assembly, **target) + # # Exceptions # @@ -575,7 +611,7 @@ def quantize(img, dither=False): return img -def r5g6b5(img, color_count=0): +def r5g6b5(img, color_count=0, alpha=None): """ Convert a PIL.Image.Image into an R5G6B5 byte stream. If there are transparent pixels, chooses a color to implement alpha and replaces them @@ -588,6 +624,10 @@ def r5g6b5(img, color_count=0): encoded with a palette of this size. Returns the converted image as a bytearray, the palette as a bytearray, and the alpha value (None if there were no transparent pixels). + + If alpha is provided, it should be a pair (alpha value, replacement). + Trandarpent pixels will be encoded with the specified alpha value and + pixels with the value will be encoded with the replacement. """ def rgb24to16(r, g, b): @@ -601,6 +641,7 @@ def r5g6b5(img, color_count=0): alpha_channel = img.getchannel("A").convert("1", dither=Image.NONE) alpha_levels = { t[1]: t[0] for t in alpha_channel.getcolors() } has_alpha = 0 in alpha_levels + replacement = None if has_alpha: alpha_pixels = alpha_channel.load() @@ -622,7 +663,10 @@ def r5g6b5(img, color_count=0): # Choose an alpha color - if color_count > 0: + if alpha is not None: + alpha, replacement = alpha + + elif color_count > 0: # Transparency is mapped to the last palette element, if there are no # transparent pixels then select an index out of bounds. alpha = color_count - 1 if has_alpha else 0xffff @@ -646,6 +690,15 @@ def r5g6b5(img, color_count=0): else: alpha = None + def alpha_encoding(color, a): + if a > 0: + if color == alpha: + return replacement + else: + return color + else: + return alpha + # Create a byte array with all encoded pixels pixel_count = img.width * img.height @@ -669,13 +722,13 @@ def r5g6b5(img, color_count=0): a = alpha_pixels[x, y] if has_alpha else 0xff if not color_count: - c = rgb24to16(*pixels[x, y]) if a > 0 else alpha + c = alpha_encoding(rgb24to16(*pixels[x, y]), a) encoded[offset] = c >> 8 encoded[offset+1] = c & 0xff offset += 2 elif color_count == 16: - c = pixels[x, y] if a > 0 else alpha + c = alpha_encoding(pixels[x, y], a) # Aligned pixels: left 4 bits = high 4 bits of current byte if (entries % 2) == 0: @@ -686,7 +739,7 @@ def r5g6b5(img, color_count=0): offset += 1 elif color_count == 256: - c = pixels[x, y] if a > 0 else alpha + c = alpha_encoding(pixels[x, y], a) encoded[offset] = c offset += 1 @@ -739,14 +792,21 @@ def convert(input, params, target, output=None, model=None): raise FxconvError(f"missing type in conversion '{input}'") elif params["type"] == "binary": convert_binary(input, output, params, target) - elif params["type"] == "image" and model in [ "fx", None ]: + elif params["type"] == "bopti-image" and model in [ "fx", None ]: convert_bopti_fx(input, output, params, target) - elif params["type"] == "image" and model == "cg": + elif params["type"] == "bopti-image" and model == "cg": convert_bopti_cg(input, output, params, target) elif params["type"] == "font": convert_topti(input, output, params, target) + elif params["type"] == "libimg-image" and model in [ "fx", None ]: + raise FxconvError(f"libimg not yet supported for fx-9860G o(x_x)o") + elif params["type"] == "libimg-image" and model == "cg": + convert_libimg_cg(input, output, params, target) + else: + raise FxconvError(f'unknown resource type \'{params["type"]}\'') -def elf(data, output, symbol, toolchain=None, arch=None, section=None): +def elf(data, output, symbol, toolchain=None, arch=None, section=None, + assembly=None): """ Call objcopy to create an object file from the specified data. The object file will export three symbols: @@ -773,6 +833,10 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None): would be section=".rodata,contents,alloc,load,readonly,data", which is the default. + If assembly is set to a non-empty assembly program, this function also + generates a temporary ELF file by assembling this piece of code, and merges + it into the original one. + Arguments: data -- A bytes-like object with data to embed into the object file output -- Name of output file @@ -780,6 +844,7 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None): toolchain -- Target triplet [default: "sh3eb-elf"] arch -- Target architecture [default: try to guess] section -- Target section [default: above variation of .rodata] + assembly -- Additional assembly code [default: None] Produces an output file and returns nothing. """ @@ -802,21 +867,42 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None): raise FxconvError(f"non-trivial architecture for {toolchain} must be "+ "specified") - with tempfile.NamedTemporaryFile() as fp: - fp.write(data) - fp.flush() + fp_obj = tempfile.NamedTemporaryFile() + fp_obj.write(data) + fp_obj.flush() - sybl = "_binary_" + fp.name.replace("/", "_") + if assembly is not None: + fp_asm = tempfile.NamedTemporaryFile() + fp_asm.write(assembly.encode('utf-8')) + fp_asm.flush() - objcopy_args = [ - f"{toolchain}-objcopy", "-I", "binary", "-O", "elf32-sh", - "--binary-architecture", arch, "--file-alignment", "4", - "--rename-section", f".data={section}", - "--redefine-sym", f"{sybl}_start={symbol}", - "--redefine-sym", f"{sybl}_end={symbol}_end", - "--redefine-sym", f"{sybl}_size={symbol}_size", - fp.name, output ] - - proc = subprocess.run(objcopy_args) + proc = subprocess.run([ + f"{toolchain}-as", "-c", fp_asm.name, "-o", fp_asm.name + ".o" ]) if proc.returncode != 0: - raise FxconvError(f"objcopy returned {proc.returncode}") + raise FxconvError(f"as returned {proc.returncode}") + + sybl = "_binary_" + fp_obj.name.replace("/", "_") + + objcopy_args = [ + f"{toolchain}-objcopy", "-I", "binary", "-O", "elf32-sh", + "--binary-architecture", arch, "--file-alignment", "4", + "--rename-section", f".data={section}", + "--redefine-sym", f"{sybl}_start={symbol}", + "--redefine-sym", f"{sybl}_end={symbol}_end", + "--redefine-sym", f"{sybl}_size={symbol}_size", + fp_obj.name, output if assembly is None else fp_obj.name + "-tmp" ] + + proc = subprocess.run(objcopy_args) + if proc.returncode != 0: + raise FxconvError(f"objcopy returned {proc.returncode}") + + if assembly is not None: + proc = subprocess.run([ + f"{toolchain}-ld", "-r", fp_obj.name + "-tmp", fp_asm.name + ".o", + "-o", output ]) + if proc.returncode != 0: + raise FxconvError("ld returned {proc.returncode}") + + fp_asm.close() + + fp_obj.close() diff --git a/fxsdk/assets/Makefile b/fxsdk/assets/Makefile index 94fd233..1106c98 100755 --- a/fxsdk/assets/Makefile +++ b/fxsdk/assets/Makefile @@ -136,10 +136,10 @@ build-cg/%.S.o: %.S # Images build-fx/assets/img/%.o: assets-fx/img/% @ mkdir -p $(dir $@) - fxconv -i $< -o $@ $(FXCONVFX) name:img_$(basename $*) $(IMG.$*) + fxconv --bopti-image $< -o $@ $(FXCONVFX) name:img_$(basename $*) $(IMG.$*) build-cg/assets/img/%.o: assets-cg/img/% @ mkdir -p $(dir $@) - fxconv -i $< -o $@ $(FXCONVCG) name:img_$(basename $*) $(IMG.$*) + fxconv --bopti-image $< -o $@ $(FXCONVCG) name:img_$(basename $*) $(IMG.$*) # Fonts build-fx/assets/fonts/%.o: assets-fx/fonts/%