vxSDK - 0.12.0-42 : remove assets converter and project module

*update*
> [vxsdk]
  | [conv] remove converter module
  | [project] remove project module

*fix*
> [install]
  | allow force install (useful when python major version update break pip env)
This commit is contained in:
Yann MAGNIN 2023-05-11 19:01:27 +02:00
parent 1a14f4ca4e
commit 7766bd6bd7
20 changed files with 24 additions and 1685 deletions

View File

@ -1,13 +0,0 @@
# Build files
/build-fx
/build-cg
/*.g1a
/*.g3a
# Python bytecode
__pycache__/
# Common IDE files
*.sublime-project
*.sublime-workspace
.vscode

View File

@ -1,92 +0,0 @@
#! /usr/bin/make -f
#
# variable definition
#
# color definition, for swagg :D
red := \033[1;31m
green := \033[1;32m
blue := \033[1;34m
white := \033[1;37m
nocolor := \033[1;0m
src := $(foreach path,\
$(shell find src -not -path "*/\.*" -type d), \
$(wildcard $(path)/*.c) \
$(wildcard $(path)/*.S) \
$(wildcard $(path)/*.s))
obj := $(patsubst src_%,$(VXSDK_PREFIX_BUILD)/%.o,$(subst /,_,$(src)))
obj += $(patsubst \
$(VXSDK_ASSETS_SRC)/%,\
$(VXSDK_ASSETS_BUILD)/%.o,\
$(wildcard $(VXSDK_ASSETS_SRC)/*.c) \
)
cflags := -ffreestanding -nostdlib -m4-nofpu -fPIE -O1
cflags += -mb -fstrict-volatile-bitfields
cflags += $(VXSDK_CFLAGS_INCLUDE) -I.. -Iinclude
# debug vars
VERBOSE ?= false
#
# build rules
#
vxaddin: $(obj)
@ printf "$(blue)Create $(red)$@$(nocolor)\n"
sh-elf-vhex-gcc \
-T $(VXSDK_PREFIX_LIB)/fxcg50-dynamic.ld -Wl,-q -Wl,-M \
$(VXSDK_CFLAGS_LINK) \
-o $@ $^ \
-lvhex-fxcg50 -lc -lgcc \
> $(VXSDK_PREFIX_BUILD)/map.txt
vxsdk conv addin -b $@ -n vxaddin -o /tmp/vxaddin
version:
@echo "$(VXSDK_PKG_VERSION)"
help:
@ echo 'Rules listing:'
@ echo '... all the default, if no target is provided'
@ echo '... clean remove build object'
@ echo '... fclean remove all generated object'
@ echo '... re same as `make fclean all`'
@ echo '... version display version'
@ echo '... install install the library'
@ echo '... uninstall uninstall the library'
.PHONY: help version
#
# Object rules
#
$(VXSDK_PREFIX_BUILD)%.o:
ifeq ($(VERBOSE),true)
@ mkdir -p $(dir $@)
sh-elf-vhex-gcc \
$(cflags) -D FXCG50 \
-o $@ \
-c $(addprefix src/,$(subst _,/,$(notdir $(basename $@))))
else
@ mkdir -p $(dir $@)
@ printf "$(green)>$(nocolor) $(white)$@$(nocolor)\n"
@ sh-elf-vhex-gcc \
$(cflags) -D FXCG50 \
-o $@ \
-c $(addprefix src/,$(subst _,/,$(notdir $(basename $@))))
endif
$(VXSDK_ASSETS_BUILD)%.o: $(VXSDK_ASSETS_SRC)/%
ifeq ($(VERBOSE),true)
@ mkdir -p $(dir $@)
sh-elf-vhex-gcc $(cflags) -D FXCG50 -o $@ -c $<
else
@ mkdir -p $(dir $@)
@ printf "$(green)>$(nocolor) $(white)$@$(nocolor)\n"
@ sh-elf-vhex-gcc $(cflags) -D FXCG50 -o $@ -c $<
endif

View File

@ -1,14 +0,0 @@
#include <vhex/display.h>
#include <vhex/keyboard.h>
int main(void)
{
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "Sample fxSDK add-in.");
dupdate();
while (1) { __asm__("sleep"); }
//getkey();
return 1;
}

View File

@ -1,8 +0,0 @@
[project]
name = 'vxaddin'
[dependencies]
vxKernel = 'dev'
[build]
build = 'make'

View File

@ -20,6 +20,7 @@ Actions:
uninstall Uninstall the VxSDK
Options:
--force Force reinstallation
--prefix=<PREFIX> Installation prefix (default is ~/.local)
-h, --help Display this help
OEF
@ -33,6 +34,7 @@ OEF
#
target='install'
force_reinstall='false'
for arg; do case "$arg" in
--help | -h)
@ -40,6 +42,9 @@ for arg; do case "$arg" in
-v | --version)
echo "$VERSION"
exit 0;;
--force)
force_reinstall='true'
;;
install)
target='install';;
@ -65,7 +70,7 @@ then
if [[ -d "$prefix/lib/vxsdk/vxsdk" ]]
then
echo 'warning : vxsdk is already installed !' >&2
read -n 1 -p 'Do you whant to re-install the vxSDK [y/N] ? ' reinstall
read -n 1 -p 'Do you whant to re-install the vxSDK [y/N] ? ' -r reinstall
[[ "$reinstall" != 'y' ]] && exit 1
echo ''
"$projdir/install.sh" uninstall-cached
@ -79,11 +84,11 @@ then
"$prefix/lib/vxsdk/"
install -d "$prefix/bin"
echo '#! /usr/bin/env bash' > $prefix/bin/vxsdk
echo '' >> $prefix/bin/vxsdk
echo "source $prefix/lib/vxsdk/venv/bin/activate" >> $prefix/bin/vxsdk
echo "python3 $prefix/lib/vxsdk/vxsdk \$@" >> $prefix/bin/vxsdk
echo 'deactivate' >> $prefix/bin/vxsdk
echo '#! /usr/bin/env bash' > "$prefix/bin/vxsdk"
echo '' >> "$prefix/bin/vxsdk"
echo "source $prefix/lib/vxsdk/venv/bin/activate" >> "$prefix/bin/vxsdk"
echo "python3 $prefix/lib/vxsdk/vxsdk \$@" >> "$prefix/bin/vxsdk"
echo 'deactivate' >> "$prefix/bin/vxsdk"
chmod +x "$prefix/bin/vxsdk"
build_date=$(date '+%Y-%m-%d')
@ -94,17 +99,17 @@ then
-e "s*%BUILD_HASH%*$build_hash*" \
-e "s*%BUILD_DATE%*$build_date*" \
"$projdir/vxsdk/__main__.py" \
> $f
> "$f"
mkdir -p $prefix/share/vxsdk
mkdir -p "$prefix/share/vxsdk"
cd $prefix/lib/vxsdk
if [[ ! -d venv ]];
cd "$prefix/lib/vxsdk" || exit 1
if [[ ! -d venv ]] || [[ "$force_reinstall" == 'true' ]]
then
set -x; python3 -m venv venv; set +x
source venv/bin/activate
set -x
pip install --upgrade pip 2>&1 > /dev/null
{ pip install --upgrade pip > /dev/null ; } 2>&1
pip install -r requirements.txt
set +x
deactivate
@ -121,9 +126,9 @@ fi
if [[ "$target" = 'uninstall-cached' ]]
then
set -x
rm $prefix/bin/vxsdk
rm -rf $prefix/lib/vxsdk/vxsdk
rmdir $prefix/share/vxsdk 2>/dev/null || exit 0
rm "$prefix/bin/vxsdk"
rm -rf "$prefix/lib/vxsdk/vxsdk"
rmdir "$prefix/share/vxsdk" 2>/dev/null || exit 0
echo 'vxSDK has been partially removed'
exit 0
fi
@ -134,9 +139,9 @@ then
# TODO : rm -rf dependencies too
# TODO : add confirmation input
rm $prefix/bin/vxsdk
rm -rf $prefix/lib/vxsdk
rmdir $prefix/share/vxsdk 2>/dev/null || exit 0
rm "$prefix/bin/vxsdk"
rm -rf "$prefix/lib/vxsdk"
rmdir "$prefix/share/vxsdk" 2>/dev/null || exit 0
echo 'vxSDK has been removed'
exit 0

View File

@ -1,65 +0,0 @@
"""
cli.conv - assset conversion abstraction
This package provides conversion abstraction (image -> source code, ELF ->
addin, ...) for the Vhex project.
"""
from core.logger import log
from cli.conv.doctor import doctor_conv_cli
from cli.conv.assets import assets_conv_cli
from cli.conv.addin import addin_conv_cli
__all__ = [
'__VXSDK_MODULE_META__',
'cli_validate',
'cli_parse',
]
#---
# Public
#---
__VXSDK_MODULE_META__ = (
['conv'],
'assets converter',
"""vxsdk-conv
Project assets converter
USAGE:
vxsdk conv(-<ACTION>) [OPTIONS] ...
DESCRIPTION:
Convert vhex project assets (or binary) into various form. By default, if
no action is specified, the "asset" conversion is selected.
ACTIONS:
asset convert asset into source file or binary file
addin convert binary into addin file for vxOS
doctor try to display assets and addin information (debug)
See `vxsdk conv-<action> --help` for more information on a specific action
"""
)
def cli_validate(name):
""" validate the module name """
return name.find('conv') == 0
def cli_parse(argv):
""" Build subcommand entry """
if '--help' in argv or '-h' in argv:
log.user(__VXSDK_MODULE_META__[2])
return 0
if argv[0].find('conv-') != 0:
argv[0] = 'conv-asset'
action = argv[0][5:]
if action == 'doctor':
return doctor_conv_cli(argv[1:])
if action == 'asset':
return assets_conv_cli(argv[1:])
if action == 'addin':
return addin_conv_cli(argv[1:])
log.error(f"unable to find action '{action}'")
return 84

View File

@ -1,69 +0,0 @@
"""
cli.conv.addin - vxSDK addin conversion
"""
import sys
#from core.conv.addin import generate_addin
from core.logger import log
__all__ = [
'addin_conv_cli'
]
#---
# Internals
#---
__HELP__ = """vxsdk-converter-addin
Converte binary file into Vhex OS addin.
USAGE:
vxsdk conv addin -b BINARY ...
DESCRIPTION:
Convert a binary file into an application for the Vhex operating system.
OPTIONS:
-b <binary path> ELF file (no check is performed in this file)
-i <icon path> 92x62 pixel image path
-o <output path> output path for the generated addin
-n <internal name> internal addin name
-v <internal version> internal addin version
"""
#---
# Public
#---
def addin_conv_cli(argv):
"""Process CLI arguments"""
if '-h' in argv or '--help' in argv:
log.user(__HELP__)
sys.exit(0)
action = None
info = [None, None, None, None, None]
for arg in argv:
if action == '-b':
info[0] = arg
if action == '-i':
info[1] = arg
if action == '-n':
info[2] = arg
if action == '-o':
info[3] = arg
if action == '-v':
info[4] = arg
if action:
action = None
continue
if arg in ['-b', '-i', '-n', '-o', '-v']:
action = arg
continue
if not info[0]:
log.error('converter: need binary path !')
sys.exit(84)
log.error('not supported addin convertion')
#return generate_addin(info[0], info[1], info[2], info[3], info[4])

View File

@ -1,75 +0,0 @@
"""
cli.conv.assets - Vhex asset converter user interface
"""
import os
from core.logger import log
from core.conv import assets_generate
__all__ = [
'assets_conv_cli'
]
#---
# Internals
#---
__HELP__ = """vxsdk-converter-asset
Convert all assets file in the project directory.
USAGE:
vxsdk conv-asset [project path] [OPTIONS]
DESCRIPTION:
Convert all assets file in the asset directory. This part of the converter
module will scan the provided folder (or the current working directory) and
will try to find all `vxconv.txt` file, which describe all assets that
should be converted.
If no argument is provided, then the current working directory is used as
asset prefix and a storag for all generated source file. You can modify
this behaviour using OPTIONS.
For more information about the `vxconv.txt` in the wiki.
OPTIONS:
-o <output prefix> The prefix for source file that will be generated
-h, --help Display this help
"""
#---
# Public
#---
def assets_conv_cli(argv):
"""Process CLI arguments"""
# check obvious flags
if '-h' in argv or '--help' in argv:
log.user(__HELP__)
return 0
# fetch user indication
manual_output = False
prefix_output = None
prefix_asset = None
for arg in argv:
if arg == '-o':
manual_output = True
continue
if manual_output:
prefix_output = arg
continue
if prefix_asset:
log.warn(f"warning: previous path ({prefix_asset}) dropped")
prefix_asset = arg
# check indication
if not prefix_asset:
prefix_asset = os.getcwd()
if not prefix_output:
prefix_output = os.getcwd()
prefix_asset = os.path.abspath(prefix_asset)
prefix_output = os.path.abspath(prefix_output)
# generate asset information
return assets_generate(prefix_asset, prefix_output, True)

View File

@ -1,24 +0,0 @@
"""
cli.conv.doctor - vxSDK converter doctor.
"""
import sys
from core.logger import log
__all__ = [
'doctor_conv_cli'
]
#---
# Public
#---
def doctor_conv_cli(_):
"""Process CLI handling
TODO:
> give asset file description to check error
> try to display asset and addin information based on the project type
"""
log.warn('conv: doctor action not implemented yet')
sys.exit(84)

View File

@ -1,54 +0,0 @@
"""
cli.project - handles project creation and more
"""
import sys
from core.project import new_project
from core.logger import log
__all__ = [
'__VXSDK_MODULE_META__',
'cli_validate',
'cli_parse'
]
#---
# Public
#---
__VXSDK_MODULE_META__ = (
['p', 'project'],
"project abstraction",
r"""vxsdk-project
Abstract project manipulation
USAGE:
vxsdk project <COMMAND> [OPTIONS]
OPTIONS:
-h, --help Print helps information
Common used commands:
n, new Create a new project
See `vxsdk project <action> --help` for more information on a specific command
"""
)
def cli_validate(name):
""" validate the module name """
return name in __VXSDK_MODULE_META__[0]
def cli_parse(argv):
""" Project subcommand entry """
if len(argv) > 1:
if '-h' in argv or '--help' in argv:
log.user(__VXSDK_MODULE_META__[2])
sys.exit(0)
if len(argv) > 3:
if argv[0] in ['n', 'new']:
for path in argv[1:]:
new_project(path)
sys.exit(0)
log.error(__VXSDK_MODULE_META__[2])
sys.exit(84)

View File

@ -2,8 +2,6 @@
core.build.compile - Compilation hadling using the dependency DAG graph
"""
import core.conv
from core.logger import log
from core.build.rules import rules_project_exec
@ -15,20 +13,6 @@ __all__ = [
# Internals
#---
def _dep_generate_assets(dep_info, _):
dep_meta = dep_info['meta']
counter = 0
for prefix in dep_meta.get_assets_prefix_list():
if counter == 0:
log.user(f"[{dep_meta.name}] generate assets...")
core.conv.assets_generate(
prefix,
f"{dep_meta.build_prefix}/converter/{dep_meta.name}",
force_generate=False
)
counter += 1
return 0
def _dep_build_sources(dep_info, verbose):
log.user(f"[{dep_info['meta'].name}] build sources...")
return rules_project_exec(
@ -58,15 +42,12 @@ def _compile_dependency(dep, verbose):
> 0 on success, negative value otherwise
"""
dep_meta = dep['info']['meta']
if _dep_generate_assets(dep['info'], verbose) != 0:
log.error(f"[{dep_meta.name}] error during asset generation")
return -1
if _dep_build_sources(dep['info'], verbose) != 0:
log.error(f"[{dep_meta.name}] error during source build")
return -2
return -1
if _dep_install(dep['info'], verbose) != 0:
log.error(f"[{dep_meta.name}] error during installation")
return -3
return -2
return 0
#---

View File

@ -1,72 +0,0 @@
"""
core.conv - Vhex converter module
"""
from core.conv.assets import assets_generate
__all__ = [
'generate_assets'
]
#---
# Public
#---
def generate_assets(prefix_assets, prefix_src, force_generate=True):
r"""Generate Vhex assets.
This function abstract the asset convertion for the Vhex Operating System.
It will walk througt the `<source_prefix>` folder and will try to find some
files named 'vxconv.txt' wich discribe assets information of a potential
project. Then it will use them to convert assets into an appropriate source
file in C without using any Vhex-specific function (so, you can use this
converter for other project).
The vxconv.txt file is structured like basic key/value file:
```
<exposed_symbols_name>:
type: <image type> (font, bitmap) - required
path: <image path> - required
...
<next_exposed_symbols_name>:
...
```
Each asset file description should have at least type and name information,
and each type have potentially its own requierements.
type = bitmap:
================================== ========================================
Keys name and value type Description
================================== ========================================
profile: <name> Select the bitmap pixel profile
| rgb4 | RGB 4 (indexed)
| rgb4a | RGBA 4 (indexed)
| rgb8 | RGB 8 (indexed)
| rgb8a | RGBA 8 (indexed)
| rgb16 | RGB 16 (5:R, 6:G, 5:B)
| rgb16a | RGBA 16 (5:R, 5:G, 5:B, 1:A)
================================== ========================================
type = font:
================================== ========================================
Keys name and value type Description
================================== ========================================
grid.size: 8x9 (widthxheight) caracter size in pixel
grid.padding: <pixel> space between caracter
grig.border: <pixel> space around grid
proportional: <true,false> caracter are cropped
line_height: <pixel> caracter line alignement
charset: <print,unicode> charset specification
================================== ========================================
@args:
> path (str) - the path to find assets
> source_prefix (str) - the path to generate image source file
@return:
> a list of string which represents all assets sources files path
"""
return assets_generate(prefix_assets, prefix_src, force_generate)

View File

@ -1,67 +0,0 @@
#from core.conv.pixel import rgba8conv
#
#from PIL import Image
#import os
#
#__all__ = [
# 'generate_addin'
#]
#
#
#def generate_addin(binary, icon=None, name=None, output=None, version=None):
# r"""Generate an addin for the Vhex Operating System.
#
# The addin name (passed through the `name` argument) is optional here. In
# this case, the name will use the internal name...which can be guessed using
# the binary name (e.i '/path/to/the/super_addin.elf' -> internal name =
# 'super_addin' -> output name = '/path/to/the/super_addin').
#
# The output path for the generated file is, by defautl, the same path that
# the binary but the suffix '.vxos' will be added.
#
# if the icon is not specified, a default blank icon will be used.
#
# Args:
# > binary (str) - binary path
# > icon (str) - addin icon path (optional)
# > name (str) - addin name (displayed in the menu) (optional)
# > output (str) - output path for the generated addin (optional)
#
# _fixme:
# > generate default internal name
# > change 8-bits icon into rgb565
# > add internal addin version in the header
# """
# if not os.path.exists(binary):
# logger(LOG_ERR, 'binary path is invalid')
# sys.exit(84)
# if icon and not os.path.exists(icon):
# logger(LOG_WARN, f'{icon}: icon does not exists, ignored')
# icon = None
# if not name:
# name = ''
# if not output:
# output = binary + '.vxos'
#
# if icon:
# bitmap = Image.open(icon)
# if bitmap.size != (92, 64):
# logger(
# LOG_ERR,
# f'{icon}:icon size does not match {bitmap.size} != (92, 64)',
# exit=84
# )
#
# with open(binary, 'rb') as b:
# with open(output, 'wb') as a:
# a.write(b'VHEX')
# a.write(name.encode('utf8'))
# a.write(b'\x00')
# if icon:
# for pixel in bitmap.getdata():
# a.write(rgba8conv(pixel).to_bytes(1, 'big'))
# else:
# a.write(bytes(92*64))
# a.write(b.read())
#
# return 0

View File

@ -1,132 +0,0 @@
"""
core.conv.assets - Vhex assets converter
"""
import os
import toml
from core.logger import log
from core.conv.type.font import font_generate
from core.conv.type.image import image_generate
__all__ = [
'assets_generate'
]
#---
# Internals
#---
class _VxAssetException(Exception):
""" simple exception wrapper """
class _VxAsset():
"""Represent a asset object
This is an internal class which represents assets information with some
methods to abstract conversion and file type manipulation (for asset type
font and asset type bitmap).
Also note that this class is private because we use a tricky optimization
to parse the `vxconv.txt` file, this is why we have no "private" property
with setter and getter, and why this class is "hidden".
Some important methods to note:
================================== =======================================
Name Description
================================== =======================================
generate() Generate the source file (C)
================================== =======================================
"""
def __init__(self, prefix, name, meta):
if 'path' not in meta:
raise _VxAssetException(f"[{name}] missing path information")
if 'type' not in meta:
raise _VxAssetException(f"[{name}] missing type information")
if meta['type'] not in ['font', 'image']:
raise _VxAssetException(f"asset type '{meta[type]}' is not known")
self._name = name
self._meta = meta
self._type = meta['type']
self._path = prefix + '/' + meta['path']
if not os.path.exists(self.path):
raise _VxAssetException(
"asset path '{self._path}' cannot be openned"
)
def __repr__(self):
return f'<_VxAssetObj, {self.name}>'
def __str__(self):
content = f"[{self.name}]\n"
content += f" - type: {self.type}\n"
content += f" - path: {self.path}\n"
return content
#---
# Getter
#---
@property
def path(self):
"""<property> path"""
return self._path
@property
def name(self):
"""<property> name"""
return self._name
@property
def type(self):
"""<property> type"""
return self._type
@property
def meta(self):
"""<property> meta"""
return self._meta
#---
# Public method
#---
def generate_source_file(self, prefix_output, force_generate):
"""generate source file """
if self.type == 'font':
return font_generate(self, prefix_output, force_generate)
return image_generate(self, prefix_output, force_generate)
#---
# Public
#---
def assets_generate(prefix_assets, prefix_output, force_generate):
""" Walk through the assets prefix and generate all source file
@args
> prefix_asset (str) - prefix used for recursivly search for asset info
> prefix_output (str) - prefix used for the output of generated file
> force_generate (bool) - force generate the source file
@return
> a list of all generated sources pathname
"""
if not os.path.exists(prefix_output):
os.makedirs(prefix_output)
generated = []
for root, _, files in os.walk(prefix_assets):
if 'vxconv.toml' not in files:
continue
with open(f"{root}/vxconv.toml", 'r', encoding='utf-8') as inf:
content = toml.loads(inf.read())
for asset_name in content:
log.user(f"converting {asset_name}...")
asset = _VxAsset(root, asset_name, content[asset_name])
generated += asset.generate_source_file(
prefix_output,
force_generate
)
return generated

View File

@ -1,45 +0,0 @@
"""
core.conv.pixel - Pixel converter utilities
"""
__all__ = [
'pixel_rgb24to16'
]
#---
# Public
#---
def pixel_rgb24to16(rgb):
""" convert RGB24 -> RGB16-565"""
_r = (rgb[0] & 0xff) >> 3
_g = (rgb[1] & 0xff) >> 2
_b = (rgb[2] & 0xff) >> 3
return (_r << 11) | (_g << 5) | _b
#def rgb1conv(pixel):
# """ Convert RGB<BLACK> -> 1-bit color """
# return pixel == (0, 0, 0)
#def rgb8conv(pixel):
# """ Convert RGB8 -> """
# return int((pixel[0] * 7) / 255) << 5 \
# | int((pixel[1] * 4) / 255) << 3 \
# | int((pixel[2] * 7) / 255) << 0
#def rgba8conv(pixel):
# return int((pixel[0] * 4) / 256) << 6 \
# | int((pixel[1] * 8) / 256) << 3 \
# | int((pixel[2] * 4) / 256) << 1 \
# | (len(pixel) >= 4 and pixel[3] == 0)
#def rgb16conv(pixel):
# return int((pixel[0] * 31) / 255) << 11 \
# | int((pixel[1] * 63) / 255) << 5 \
# | int((pixel[2] * 31) / 255) << 0
#def rgba16conv(pixel):
# return int((pixel[0] * 31) / 255) << 11 \
# | int((pixel[1] * 63) / 255) << 6 \
# | int((pixel[2] * 31) / 255) << 1 \
# | (pixel[3] != 0)

View File

@ -1,468 +0,0 @@
"""
core.conv.type.font - Vhex font converter
"""
import os
from PIL import Image
from core.logger import log
__all__ = [
'font_generate'
]
#---
# Internals
#---
## Font meta info
def _font_fetch_info(asset):
""" Check and fetch font information
@arg
> asset (VxAsset) - asset information
@return
> dictionary with font information
"""
# generate font default information
font_info = {
# user can customise
'charset' : 'normal',
'grid_size_x' : 0,
'grid_size_y' : 0,
'grid_padding' : 1,
'grid_border' : 1,
'is_proportional' : False,
'line_height' : 0,
'char_spacing' : 1,
# generated "on-the-fly" by the conversion step
# @notes
# This is mainly to provide cache for the Vhex operating system to
# speed-up render calculation by avoiding recurent caculation.
'glyph_size' : 0,
'glyph_height' : 0,
'font_size' : 0,
'data' : []
}
# handle user meta-indication
if 'charset' in asset.meta:
if asset.meta['charset'] not in ['default', 'unicode']:
log.error(f"Unknown charset '{asset.meta['charset']}', abord")
return None
font_info['charset'] = asset.meta['charset']
if 'grid_size' not in asset.meta:
log.error("Missing critical grid size information, abord")
return None
grid_size = asset.meta['grid_size'].split('x')
font_info['grid_size_x'] = int(grid_size[0])
font_info['grid_size_y'] = int(grid_size[1])
if 'grid_padding' in asset.meta:
font_info['grid_padding'] = int(asset.meta['grid_padding'])
if 'grid_border' in asset.meta:
font_info['grid_border'] = int(asset.meta['grid_border'])
if 'proportional' in asset.meta:
font_info['is_proportional'] = asset.meta['proportional']
font_info['line_height'] = font_info['grid_size_y']
if 'line_height' in asset.meta:
font_info['line_height'] = asset.meta['line_height']
if 'char_spacing' in asset.meta:
font_info['char_spacing'] = asset.meta['char_spacing']
font_info['glyph_height'] = font_info['grid_size_y']
# return font information
return font_info
## Glyph handling
def _glyph_get_wgeometry(geometry_info, img_raw, img_size, pos, grid_size):
""" Generate glyph width geometry information
@args
> geometry_info (dict) - geometry information
> img_raw (list) - list of all pixel of the image
> img_size (tuple) - image width and image height
> pos (tuple) - glyph position information (X and Y in pixel)
> grid_size (tuple) - glyph grid size information (width and height)
@return
> Nothing
"""
geometry_info['wstart'] = -1
geometry_info['wend'] = -1
_px = pos[0]
_py = pos[1]
log.debug(f'[geometry] X:{pos[0]} Y:{int(pos[1]/img_size[0])}')
log.debug(f' - grid_size = {grid_size}')
for _ in range(0, grid_size[1]):
for offx in range(0, grid_size[0]):
if img_raw[_py + (_px + offx)][:3] == (255, 255, 255):
continue
if geometry_info['wstart'] < 0 or offx < geometry_info['wstart']:
geometry_info['wstart'] = offx
if geometry_info['wstart'] < 0 or offx > geometry_info['wend']:
geometry_info['wend'] = offx
_py += img_size[0]
geometry_info['wend'] += 1
log.debug(f' - geometry = {geometry_info}')
def _glyph_encode(data_info, img_info, geometry, posx, posy):
""" Encode glyph bitmap
@args
> data_info (dict) - internal data information (list, index and shift)
> img_info (dict) - image-related information (object and raw content)
> geometry (dict) - geometry information
> posx (int) - X-axis position in pixel
> posy (int) - Y-axis position in pixel
@return
> Nothing
"""
# fetch information
img = img_info['obj']
img_raw = img_info['raw']
data = data_info['table']
data_idx = data_info['idx']
data_shift = data_info['shift']
wstart = geometry['wstart']
wend = geometry['wend']
# encode the glyph
yoff = 0
log.debug(f'[encode] X:{posx} Y:{int(posy/img.size[0])}')
for _h in range(geometry['hstart'], geometry['hend']):
for _w in range(wstart, wend):
if img_raw[(posy + yoff) + (posx + _w)][:3] == (0, 0, 0):
log.debug('#', end='')
data[data_idx] |= 0x80000000 >> data_shift
else:
log.debug('.', end='')
data[data_idx] &= ~(0x80000000 >> data_shift)
if (data_shift := data_shift + 1) >= 32:
data_shift = 0
data_idx += 1
log.debug('')
yoff += img.size[0]
# commit modification
data_info['idx'] = data_idx
data_info['shift'] = data_shift
## Font convert handling
def _font_convert_proportional(packed_info):
""" Generate proportional font
Proportional font means that each character have its own width size (but
have a common height). We need to performs more complexe handling than the
monospaced one.
@args
> asset (VxAsset) - asset information
> font_information (dict) - font indication
@return
> 0 if success, negative value otherwise
"""
# unpack information
font_info = packed_info[0]
img_info = packed_info[1]
glyph_info = packed_info[2]
data_info = packed_info[4]
geometry_info = packed_info[5]
# isolate needed information
img = img_info['obj']
img_raw = img_info['raw']
nb_col = packed_info[3][0]
nb_row = packed_info[3][1]
gwidth = glyph_info[0]
gheight = glyph_info[1]
# main loop, walk glyph per glyph
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
for _ in range(0, nb_row):
_px = font_info['grid_border'] + font_info['grid_padding']
for _ in range(0, nb_col):
# generate width geometry information
_glyph_get_wgeometry(
geometry_info,
img_raw,
img.size,
(_px, _py),
(font_info['grid_size_x'], font_info['grid_size_y'])
)
# save critical glyph geometry information that will be encoded in
# the final C source file
font_info['glyph_props'].append((
geometry_info['wend'] - geometry_info['wstart'],
data_info['idx'],
data_info['shift']
))
# encode glyph information
_glyph_encode(data_info, img_info, geometry_info, _px, _py)
# update loop information
font_info['glyph_count'] += 1
_px += gwidth
_py += gheight * img.size[0]
return 0
def _font_convert_monospaced(packed_info):
""" Generate proportional font
Proportional font means that each character have its own width size (but
have a common height). We need to performs more complexe handling than the
monospaced one.
@args
> asset (VxAsset) - asset information
> font_information (dict) - font indication
@return
> 0 if success, negative value otherwise
"""
# unpack information
font_info = packed_info[0]
img_info = packed_info[1]
glyph_info = packed_info[2]
grid_info = packed_info[3]
data_info = packed_info[4]
geometry_info = packed_info[5]
# isolate needed information
img = img_info['obj']
nb_row = grid_info[1]
nb_col = grid_info[0]
gwidth = glyph_info[0]
gheight = glyph_info[1]
# main loop, walk glyph per glyph
_py = (font_info['grid_border'] + font_info['grid_padding']) * img.size[0]
for _ in range(0, nb_row):
_px = font_info['grid_border'] + font_info['grid_padding']
for _ in range(0, nb_col):
_glyph_encode(data_info, img_info, geometry_info, _px, _py)
font_info['glyph_count'] += 1
_px += gwidth
_py += gheight * img.size[0]
return 0
def _font_convert(asset, font_info):
""" Generate font information
@args
> asset (VxAsset) - asset information
> font_info (dict) - font information
@return
> 0 if success, negative value otherwise
"""
# generate image information
img = Image.open(asset.path)
img_raw = img.getdata()
img_info = {
'obj' : img,
'raw' : img_raw
}
# pre-calculate the "real" glyph width and height using padding information
glyph_info = [0, 0]
glyph_info[0] = font_info['grid_size_x'] + font_info['grid_padding']
glyph_info[1] = font_info['grid_size_y'] + font_info['grid_padding']
gheight = glyph_info[1]
gwidth = glyph_info[0]
log.debug(f"gwidth = {gwidth} && gheight = {gheight}")
# pre-calculate the number of row and column of the font
grid_info = [0, 0]
grid_info[0] = int((img.size[0] - (font_info['grid_border'] * 2)) / gwidth)
grid_info[1] = int((img.size[1] - (font_info['grid_border'] * 2)) /gheight)
nb_col = grid_info[0]
nb_row = grid_info[1]
log.debug(f"nb_row = {nb_row} && nb_col = {nb_col}")
# pre-calculate and prepare per-glyph information
# @note
# The generated data is designed for 4-alignement padding. This to have
# speed-up on drawing function.
font_info['glyph_size'] = font_info['grid_size_x']
font_info['glyph_size'] *= font_info['grid_size_y']
font_info['font_size'] = font_info['glyph_size'] * nb_row * nb_col
font_info['glyph_count'] = 0
font_info['glyph_props'] = []
font_info['data'] = [0] * int((font_info['font_size'] + 31) / 32)
log.debug(f"data original = {id(font_info['data'])}")
# generate data information
data_info = {
'table' : font_info['data'],
'idx' : 0,
'shift' : 0
}
log.debug(f"data packed = {id(data_info['table'])}")
# generate geometry information
geometry_info = {
'hstart' : 0,
'hend' : font_info['grid_size_y'],
'wstart' : 0,
'wend' : font_info['grid_size_x'],
}
# select the converter
converter = _font_convert_monospaced
if font_info['is_proportional']:
converter = _font_convert_proportional
# convert font
converter((
font_info,
img_info,
glyph_info,
grid_info,
data_info,
geometry_info
))
log.debug(f"data packed end = {id(data_info['table'])}")
return 0
## Font generator
def _font_generate_unicode_source(_):
""" Unicode special chaset directory """
log.error("unicode conversion not implemented yet o(x_x)o")
return ''
def _font_generate_normal_source(font_info):
""" Print chaset is a image file
"""
content = "\t.glyph = {\n"
content += f"\t\t.height = {font_info['glyph_height']},\n"
content += f"\t\t.line_height = {font_info['line_height']},\n"
# encode font bitmap
line = 0
log.debug(f"data = {font_info['data']}")
content += "\t\t.data = (uint32_t[]){\n"
for pixel in font_info['data']:
if line == 0:
content += '\t\t\t'
if line >= 1:
content += ' '
content += f"{pixel:#010x},"
if (line := line + 1) == 4:
content += '\n'
line = 0
if line != 0:
content += '\n'
content += '\t\t},\n'
# indicate the number of glyph in the bitmap
content += f"\t\t.count = {font_info['glyph_count']},\n"
# encode proportional information if needed
if font_info['is_proportional']:
content += '\t\t.prop = (struct __workaround[]){\n'
for prop in font_info['glyph_props']:
content += "\t\t\t{\n"
content += f"\t\t\t\t.width = {prop[0]},\n"
content += f"\t\t\t\t.index = {prop[1]},\n"
content += f"\t\t\t\t.shift = {prop[2]},\n"
content += "\t\t\t},\n"
else:
content += "\t\t.mono = {,\n"
content += f"\t\t\t.width = {font_info['glyph_width']},\n"
content += f"\t\t\t.size = {font_info['glyph_size']},\n"
content += "\t\t},\n"
content += "\t},\n"
# skip unicode struct
content += "\t.unicode = {\n"
content += "\t\t.blocks = NULL,\n"
content += "\t\t.block_count = 0,\n"
content += "\t}\n"
return content
def _font_generate_source_file(asset, font_info):
""" Generate font source file content
@args
> asset (VxAsset) - asset information
> info (dict) - hold font information
@return
> file C content string
"""
# generate basic header
content = "#include <vhex/display/font.h>\n"
content += "\n"
content += f"/* {asset.name} - Vhex asset\n"
content += " This object has been converted by using the vxSDK "
content += "converter */\n"
content += f"struct font const {asset.name} = " + "{\n"
content += f"\t.name = \"{asset.name}\",\n"
# shape information
content += "\t.shape = {\n"
content += "\t\t.bold = 0,\n"
content += "\t\t.italic = 0,\n"
content += "\t\t.serif = 0,\n"
content += "\t\t.mono = 0,\n"
content += f"\t\t.prop = {int(font_info['is_proportional'])},\n"
content += "\t},\n"
# manage display indication
content += f"\t.char_spacing = {font_info['char_spacing']},\n"
# handle special charset behaviour
if font_info['charset'] == 'unicode':
content += _font_generate_unicode_source(font_info)
else:
content += _font_generate_normal_source(font_info)
# closure and return
content += '};\n'
return content
#---
# Public
#---
def font_generate(asset, prefix_output, force_generate):
""" Convert an image asset to a C source file
@args
> asset (_VxAsset) - minimal asset information
> prefix_output (str) - prefix for source file generation
> force_generate (bool) - force generate the source file
@return
> pathname of the generated file
"""
# check if the asset already exists
asset_src = f'{prefix_output}/{asset.name}_vxfont.c'
if not force_generate and os.path.exists(asset_src):
return asset_src
# generate font information
if not (font_info := _font_fetch_info(asset)):
return ''
if _font_convert(asset, font_info) != 0:
return ''
content = _font_generate_source_file(asset, font_info)
# create the source file
with open(asset_src, "w", encoding='utf8') as file:
file.write(content)
log.debug(f"source file generated at {asset_src}")
return asset_src

View File

@ -1,403 +0,0 @@
"""
core.conv.type.image - Vhex image converter
"""
import os
from PIL import Image
from core.logger import log
from core.conv.pixel import pixel_rgb24to16
__all__ = [
'image_generate'
]
#---
# Internals
#---
## Profile handling
def _profile_gen(profile, name, palette=None, alpha=None):
r""" Internal image profile class
================================== ========================================
Property Description
================================== ========================================
id (int) profile ID
names (array of str) list all profile names
format (str) profile format name (vhex API)
has_alpha (bool) indicate if the profil has alpha
alpha (int) alpha index in the palette (or mask)
is_indexed (bool) indicate if it should be indexed
palette_base (int) indicate base index for color inserting
palette_color_count (int) indicate the number of color (palette)
palette_trim (bool) indicate if the palette should be trimed
================================== ========================================
"""
profile = {
'profile' : profile,
'name' : name,
'has_alpha' : (alpha is not None),
'alpha' : alpha,
'is_indexed': (palette is not None),
'palette' : None
}
if palette is not None:
profile['palette_base'] = palette[0]
profile['palette_color_count'] = palette[1]
profile['palette_trim'] = palette[2]
return profile
# all supported profile information
VX_PROFILES = [
_profile_gen('IMAGE_RGB565', "p16"),
_profile_gen('IMAGE_RGB565A', "p16a", alpha=0x0001),
_profile_gen('IMAGE_P8_RGB565', "p8", palette=(0,256,True)),
_profile_gen('IMAGE_P8_RGB565A', "p8a", palette=(1,256,True), alpha=0),
_profile_gen('IMAGE_P4_RGB565', "p4", palette=(0,16,False)),
_profile_gen('IMAGE_P4_RGB565A', "p4a", palette=(1,16,False), alpha=0),
]
def _profile_find(name):
"""Find a profile by name."""
for profile in VX_PROFILES:
if name == profile['name']:
return profile
return None
## Image manipulation
def _image_isolate_alpha(info):
""" Isolate alpha corlor of the image
Vhex use a particular handling for alpha color and this information should
use a strict encoding way. Things that Pillow don't do properly. So, lets
manually setup our alpha isolation and patch Pillow alpha palette handling.
@args
> info (dict) - contains all needed information (image, data, ...)
@return
> Nothing
"""
# fetch needed information
img = info['img']
profile = info['profile']
# Save the alpha channel and make it 1-bit. We need to do this because
# the alpha value is handled specialy in Vhex and the image conversion
# to palette-oriented image is weird : the alpha colors is also converted
# in the palette
if profile['has_alpha']:
alpha_channel = img.getchannel("A").convert("1", dither=Image.NONE)
else:
alpha_channel = Image.new("1", img.size, 1)
alpha_pixels = alpha_channel.load()
img = img.convert("RGB")
# Transparent pixels have random values on the RGB channels, causing
# them to use up palette entries during quantization. To avoid that, set
# their RGB data to a color used somewhere else in the image.
pixels = img.load()
bg_color = next(
(
pixels[x,y]
for x in range(img.width)
for y in range(img.height)
if alpha_pixels[x,y] > 0
),
(0,0,0)
)
for _y in range(img.height):
for _x in range(img.width):
if alpha_pixels[_x, _y] == 0:
pixels[_x, _y] = bg_color
# update external information
info['img'] = img
info['img_pixel_list_alpha'] = alpha_pixels
info['img_pixel_list_clean'] = pixels
def _image_encode_palette(info):
""" Generate palette information
This routine is involved only if the targeted profile is indexed. We need
to generate (and isolate) color palette.
@args
> info (dict) - contains all needed information (image, data, ...)
@return
> Nothing
"""
# fetch needed information
img = info['img']
profile = info['profile']
# convert image into palette format
# note: we remove one color slot in the palette for the alpha one
color_count = profile['palette_color_count'] - int(profile['has_alpha'])
img = img.convert(
'P',
dither=Image.NONE,
palette=Image.ADAPTIVE,
colors=color_count
)
# The palette format is a list of N triplets ([r, g, b, ...]). But,
# sometimes, colors after img.convert() are not numbered 0 to
# `color_count`, because the palette don't need to be that big. So,
# we calculate the "palette size" by walking throuth the bitmap and
# by saving the biggest index used.
pixels = img.load()
nb_triplet = 1 + max(
pixels[x,y]
for y in range(img.height)
for x in range(img.width)
)
palette = img.getpalette()[:3 * nb_triplet]
palette = list(zip(palette[::3], palette[1::3], palette[2::3]))
# For formats with transparency, add an "unused" palette slot which
# will used has pink/purple in case of a bad application try to use
# this value anyway
if profile['has_alpha']:
palette = [(255, 0, 255)] + palette
nb_triplet += 1
# Also keep track of how to remap indices from the values generated
# by img.convert() into the palette, which is shifted by 1 due to
# alpha and also starts at profile.palette_base.
#
# Note: profile.palette_base already starts 1 value later for
# formats with alpha.
palette_map = [
(profile['palette_base'] + i) % profile['palette_color_count']
for i in range(nb_triplet)
]
# Encode the palette
palette_color_count = nb_triplet
if not profile['palette_trim']:
palette_color_count = profile['palette_color_count']
palette_data = [0] * palette_color_count
for i, rgb24 in enumerate(palette):
palette_data[i] = pixel_rgb24to16(rgb24)
# update internal information
info['palette_map'] = palette_map
info['palette_data'] = palette_data
info['palette_color_count'] = palette_color_count
info['nb_triplet'] = nb_triplet
info['img_pixel_list_clean'] = pixels
def _image_encode_bitmap(info):
""" Encode the bitmap
This routine will generate the main data list which will contains the
bitmap using Vhex-specific encoding.
@args
> info (dict) - contains all needed information (image, data, ...)
@return
> Nothing
"""
# fetch needed information
img = info['img']
profile = info['profile']
alpha_pixels = info['img_pixel_list_alpha']
pixels = info['img_pixel_list_clean']
palette_map = info['palette_map']
# generate profile-specific geometry information
if profile['name'] in ['p16', 'p16a']:
# Preserve alignment between rows by padding to 4 bytes
nb_stride = ((img.width + 1) // 2) * 4
data_size = (nb_stride * img.height) * 2
elif profile['name'] in ['p8', 'p8a']:
nb_stride = img.width
data_size = img.width * img.height
else:
# Pad whole bytes
nb_stride = (img.width + 1) // 2
data_size = nb_stride * img.height
# Generate the real data map
data = [0] * data_size
# encode the bitmap
for _y in range(img.height):
for _x in range(img.width):
# get alpha information about this pixel
_a = alpha_pixels[_x, _y]
if profile['name'] in ['p16', 'p16a']:
# If c lands on the alpha value, flip its lowest bit to avoid
# ambiguity with alpha
_c = profile['alpha']
if not _a:
_c = pixel_rgb24to16(pixels[_x, _y]) & ~1
data[(img.width * _y) + _x] = _c
elif profile['name'] in ['p8', 'p8a']:
_c = palette_map[pixels[_x,_y]] if _a > 0 else profile['alpha']
data[(img.width * _y) + _x] = _c
else:
_c = palette_map[pixels[_x,_y]] if _a > 0 else profile['alpha']
offset = (nb_stride * _y) + (_x // 2)
if _x % 2 == 0:
data[offset] |= (_c << 4)
else:
data[offset] |= _c
# update external information
info['data'] = data
info['data_size'] = data_size
info['nb_stride'] = nb_stride
info['data_size'] = data_size
def _image_convert(asset, profile_name):
""" Image asset convertion
@args
> asset (_VxAsset) - asset information
> profile_name (str) - profile name information
@return
> a dictionary with all image information
"""
# generate critical information and check posible error
img_info = {
'img' : Image.open(asset.path),
'profile' : _profile_find(profile_name)
}
if not img_info['img']:
log.error(f"unable to open the asset '{asset.path}', abord")
return None
if not img_info['profile']:
log.error(f"unable to find the color profile '{profile_name}', abord")
return None
# convert the bitmap and generate critical information
_image_isolate_alpha(img_info)
if img_info['profile']['is_indexed']:
_image_encode_palette(img_info)
_image_encode_bitmap(img_info)
# return generated information
return img_info
## source file content generation
def _display_array(array, prefix='\t\t'):
""" Display array information (only for p16* profile) """
line = 0
content = ''
for pixels in array:
if line == 0:
content += prefix
if line >= 1:
content += ' '
content += f'{pixels:#06x},'
if (line := line + 1) >= 8:
content += '\n'
line = 0
if line != 0:
content += '\n'
return content
def _image_generate_source_file(asset, info):
"""Generate image source file
@args
> asset (VxAsset) - asset information
> info (dict) - hold image information
@return
> file C content string
"""
img = info['img']
profile = info['profile']
# generate basic header
content = "#include <vhex/display/image/types.h>\n"
content += "\n"
content += f"/* {asset.name} - Vhex asset\n"
content += " This object has been converted by using the vxSDK "
content += "converter */\n"
content += "const image_t " + f"{asset.name} = " + "{\n"
content += f"\t.format = {profile['profile']},\n"
content += "\t.flags = IMAGE_FLAGS_RO | IMAGE_FLAGS_OWN,\n"
content += f"\t.color_count = {profile['palette_color_count']},\n"
content += f"\t.width = {img.width},\n"
content += f"\t.height = {img.height},\n"
content += f"\t.stride = {info['nb_stride']},\n"
# encode bitmap table
encode = 16 if profile['profile'] in ['p16', 'p16a'] else 8
content += f"\t.data = (void*)(const uint{encode}_t [])" + "{\n"
for _y in range(img.height):
content += '\t\t'
for _x in range(info['nb_stride']):
pixel = info['data'][(_y * info['nb_stride']) + _x]
if profile['profile'] in ['p16', 'p16a']:
content += f'{pixel:#06x},'
elif profile['profile'] in ['p8', 'p8a']:
content += f'{pixel:#04x},'
else:
content += f'{pixel:3},'
content += '\n'
content += '\t},\n'
# add palette information
if 'palette_data' in info:
content += "\t.palette = (void*)(const uint16_t []){\n"
content += _display_array(info['palette_data'])
content += "\t},\n"
else:
content += "\t.palette = NULL,\n"
# closure and return
content += '};'
return content
#---
# Public
#---
def image_generate(asset, prefix_output, force_generate):
""" Convert an image asset to a C source file
@args
> asset (_VxAsset) - minimal asset information
> prefix_output (str) - prefix for source file generation
> force_generate (bool) - force generate the source file
@return
> pathname of the generated file
"""
# check critical requirement
if 'profile' not in asset.meta:
log.error(f"[{asset.name}] missing profile information!")
return ''
# check if the file already exists
asset_src = f'{prefix_output}/{asset.name}_vximage.c'
if not force_generate and os.path.exists(asset_src):
return asset_src
#generate the source file content
if not (img_info := _image_convert(asset, asset.meta['profile'])):
return ''
content = _image_generate_source_file(asset, img_info)
# generate the source file
with open(asset_src, "w", encoding='utf8') as file:
file.write(content)
return asset_src

View File

@ -1,17 +0,0 @@
"""
core.project - project abstraction
"""
from core.project.new import new_project
__all__ = [
'new'
]
#---
# Public
#---
def new(projpath):
""" create a new project from default template """
return new_project(projpath)

View File

@ -1,29 +0,0 @@
"""
core.project.new - create a new project from default template
"""
import os
import shutil
from core.logger import log
__all__ = [
'new_project'
]
#---
# Public
#---
# (todo/CDE6) : change internal project name
def new_project(project_path):
""" create a new project """
if os.path.exists(project_path):
log.warn(f"The path {project_path} already exists !")
return True
origin_path = os.path.dirname(__file__)
shutil.copytree(
origin_path + '/../../assets/project/',
project_path
)
log.user(f"project '{project_path}' successfully created !")
return False