Compare commits

...

2 Commits

Author SHA1 Message Date
Lephenixnoir c79b3b1a9d
fxconv: add suport for libimg images and deprecate --image
This commit introduces the libimg image format, selected with the option
type:libimg-image. To avoid confusion with the bopti image format,
options -i and --image are now deprecated and should be replaced with
--bopti-image or type:bopti-image. The fxSDK Makefile has been updated
accordingly.

To support the construction of a structure that contains a pointer in
fxconv, an assembly-code feature has been added. The structure itself is
assembled with as and then linked with the data proper. This allows the
structure to reference the data by name and have the pointer calculated
by ld at link time.
2020-03-11 19:37:27 +01:00
Lephenixnoir b86b96aa4a
fxsdk: split additional libs into LIBS_FX and LIBS_CG
This is because several libraries have separate binaries, and so are
linked with different command-line arguments, just like gint is linked
with -lgint-fx on one side and -lgint-cg on the other.
2020-03-10 22:39:25 +01:00
4 changed files with 136 additions and 38 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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/%

View File

@ -191,11 +191,12 @@ INCLUDE := -I include
# Libraries. Add one -l option for each library you are using, and also
# suitable -L options if you have library files in custom folders. To use
# fxlib, add libfx.a to the project directory and use "-L . -lfx".
LIBS :=
LIBS_FX :=
LIBS_CG :=
# Base linker flags for the fxSDK, you usually want to keep these.
LDFLAGS_FX := -T fx9860g.ld -lgint-fx \$(LIBS) -lgint-fx -lgcc
LDFLAGS_CG := -T fxcg50.ld -lgint-cg \$(LIBS) -lgint-cg -lgcc
LDFLAGS_FX := -T fx9860g.ld -lgint-fx \$(LIBS_FX) -lgint-fx -lgcc
LDFLAGS_CG := -T fxcg50.ld -lgint-cg \$(LIBS_CG) -lgint-cg -lgcc
# Additional linker flags, if you need any.
LDFLAGS :=