mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-29 21:52:03 -07:00
FBT: Optimize icons blob -4KB DFU, scrub unused icons (#291)
* Unused icons to check later
* Exclude disabled icons from firmware
* Format
* Also report free flash in gh comment
* Fix free flash calc
* Fix?
* Fix??
* Split to next line
* Remove dead icons
* Some spring cleaning of icons cooker
* Improve unused icons script
* Disable icons that cant be used in asset packs
* These will need a workaround for external
* Revert "These will need a workaround for external"
This reverts commit fb23d97952.
* Here's the workaround: split assets lib
now there is "assets" and "fwassets"
firmware links with fwassets and includes all icons
however not all of them are exposed to api
if an app needs a firmware icon not in api, it can use fap_libs=["assets"]
this will link against this dummy assets lib
it only contains the icons that arent exposed to api
this way, an app using assets lib will still benefit from asset packs
but at same time, we can remove pointless icons from dfu blob
* Update changelog
This commit is contained in:
+57
-35
@@ -11,11 +11,20 @@ ICONS_SUPPORTED_FORMATS = ["png"]
|
||||
|
||||
ICONS_TEMPLATE_H_HEADER = """#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <stddef.h>
|
||||
#include <gui/icon.h>
|
||||
|
||||
"""
|
||||
ICONS_TEMPLATE_H_ICON_NAME = "extern const Icon {name};\n"
|
||||
ICONS_TEMPLATE_H_ICON_PATHS = """
|
||||
typedef struct {
|
||||
const Icon* icon;
|
||||
const char* path;
|
||||
} IconPath;
|
||||
|
||||
extern const IconPath ICON_PATHS[];
|
||||
extern const size_t ICON_PATHS_COUNT;
|
||||
"""
|
||||
|
||||
ICONS_TEMPLATE_C_HEADER = """#include "{assets_filename}.h"
|
||||
|
||||
@@ -25,6 +34,15 @@ ICONS_TEMPLATE_C_HEADER = """#include "{assets_filename}.h"
|
||||
ICONS_TEMPLATE_C_FRAME = "const uint8_t {name}[] = {data};\n"
|
||||
ICONS_TEMPLATE_C_DATA = "const uint8_t* const {name}[] = {data};\n"
|
||||
ICONS_TEMPLATE_C_ICONS = "const Icon {name} = {{.width={width},.height={height},.frame_count={frame_count},.frame_rate={frame_rate},.frames=_{name}}};\n"
|
||||
ICONS_TEMPLATE_C_ICON_PATH = ' {{&{name}, "{path}"}},\n'
|
||||
ICONS_TEMPLATE_C_ICON_PATHS = """
|
||||
const IconPath ICON_PATHS[] = {{
|
||||
#ifndef FURI_RAM_EXEC
|
||||
{icon_paths}
|
||||
#endif
|
||||
}};
|
||||
const size_t ICON_PATHS_COUNT = COUNT_OF(ICON_PATHS);
|
||||
"""
|
||||
|
||||
MAX_IMAGE_WIDTH = 2**16 - 1
|
||||
MAX_IMAGE_HEIGHT = 2**16 - 1
|
||||
@@ -45,6 +63,22 @@ class Main(App):
|
||||
required=False,
|
||||
default="assets_icons",
|
||||
)
|
||||
self.parser_icons.add_argument(
|
||||
"--fw-bundle",
|
||||
dest="fw_bundle",
|
||||
help="Bundle all icons and path info, only for use in firmware blob",
|
||||
default=0,
|
||||
type=int,
|
||||
required=False,
|
||||
)
|
||||
self.parser_icons.add_argument(
|
||||
"--add-include",
|
||||
dest="add_include",
|
||||
help="Add assets_icons.h include drop-in for apps",
|
||||
default=0,
|
||||
type=int,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.parser_icons.set_defaults(func=self.icons)
|
||||
|
||||
@@ -138,7 +172,6 @@ class Main(App):
|
||||
)
|
||||
icons = []
|
||||
paths = []
|
||||
is_main_assets = self.args.filename == "assets_icons"
|
||||
symbols = pathlib.Path(__file__).parent.parent
|
||||
if "UFBT_HOME" in os.environ:
|
||||
symbols /= "sdk_headers/f7_sdk"
|
||||
@@ -154,7 +187,8 @@ class Main(App):
|
||||
if "frame_rate" in filenames:
|
||||
self.logger.debug("Folder contains animation")
|
||||
icon_name = "A_" + os.path.split(dirpath)[1].replace("-", "_")
|
||||
if not is_main_assets and api_has_icon(icon_name):
|
||||
icon_in_api = api_has_icon(icon_name)
|
||||
if not self.args.fw_bundle and icon_in_api:
|
||||
self.logger.info(
|
||||
f"{self.args.filename}: ignoring duplicate icon {icon_name}"
|
||||
)
|
||||
@@ -193,8 +227,9 @@ class Main(App):
|
||||
)
|
||||
icons_c.write("\n")
|
||||
icons.append((icon_name, width, height, frame_rate, frame_count))
|
||||
p = dirpath.removeprefix(self.args.input_directory)[1:]
|
||||
paths.append((icon_name, p.replace("\\", "/")))
|
||||
if self.args.fw_bundle and icon_in_api:
|
||||
path = dirpath.removeprefix(self.args.input_directory)[1:]
|
||||
paths.append((icon_name, path.replace("\\", "/")))
|
||||
else:
|
||||
# process icons
|
||||
for filename in filenames:
|
||||
@@ -204,7 +239,8 @@ class Main(App):
|
||||
icon_name = "I_" + "_".join(filename.split(".")[:-1]).replace(
|
||||
"-", "_"
|
||||
)
|
||||
if not is_main_assets and api_has_icon(icon_name):
|
||||
icon_in_api = api_has_icon(icon_name)
|
||||
if not self.args.fw_bundle and icon_in_api:
|
||||
self.logger.info(
|
||||
f"{self.args.filename}: ignoring duplicate icon {icon_name}"
|
||||
)
|
||||
@@ -222,8 +258,11 @@ class Main(App):
|
||||
)
|
||||
icons_c.write("\n")
|
||||
icons.append((icon_name, width, height, 0, 1))
|
||||
p = fullfilename.removeprefix(self.args.input_directory)[1:]
|
||||
paths.append((icon_name, p.replace("\\", "/").rsplit(".", 1)[0]))
|
||||
if self.args.fw_bundle and icon_in_api:
|
||||
path = fullfilename.removeprefix(self.args.input_directory)[1:]
|
||||
paths.append(
|
||||
(icon_name, path.replace("\\", "/").rsplit(".", 1)[0])
|
||||
)
|
||||
# Create array of images:
|
||||
self.logger.debug("Finalizing source file")
|
||||
for name, width, height, frame_rate, frame_count in icons:
|
||||
@@ -236,21 +275,14 @@ class Main(App):
|
||||
frame_count=frame_count,
|
||||
)
|
||||
)
|
||||
if is_main_assets:
|
||||
icons_c.write(
|
||||
"""
|
||||
const IconPath ICON_PATHS[] = {
|
||||
#ifndef FURI_RAM_EXEC
|
||||
"""
|
||||
)
|
||||
for name, path in paths:
|
||||
icons_c.write(f' {{&{name}, "{path}"}},\n')
|
||||
icons_c.write(
|
||||
"""#endif
|
||||
};
|
||||
const size_t ICON_PATHS_COUNT = COUNT_OF(ICON_PATHS);
|
||||
"""
|
||||
if not self.args.fw_bundle:
|
||||
icons_c.write("\n")
|
||||
else:
|
||||
icon_paths = "\n".join(
|
||||
ICONS_TEMPLATE_C_ICON_PATH.format(name=name, path=path)
|
||||
for name, path in paths
|
||||
)
|
||||
icons_c.write(ICONS_TEMPLATE_C_ICON_PATHS.format(icon_paths=icon_paths))
|
||||
icons_c.close()
|
||||
|
||||
# Create Public Header
|
||||
@@ -263,19 +295,9 @@ const size_t ICON_PATHS_COUNT = COUNT_OF(ICON_PATHS);
|
||||
icons_h.write(ICONS_TEMPLATE_H_HEADER)
|
||||
for name, width, height, frame_rate, frame_count in icons:
|
||||
icons_h.write(ICONS_TEMPLATE_H_ICON_NAME.format(name=name))
|
||||
if is_main_assets:
|
||||
icons_h.write(
|
||||
"""
|
||||
typedef struct {
|
||||
const Icon* icon;
|
||||
const char* path;
|
||||
} IconPath;
|
||||
|
||||
extern const IconPath ICON_PATHS[];
|
||||
extern const size_t ICON_PATHS_COUNT;
|
||||
"""
|
||||
)
|
||||
else:
|
||||
if self.args.fw_bundle:
|
||||
icons_h.write(ICONS_TEMPLATE_H_ICON_PATHS)
|
||||
if self.args.add_include:
|
||||
icons_h.write("#include <assets_icons.h>\n")
|
||||
icons_h.close()
|
||||
self.logger.debug("Done")
|
||||
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
import pathlib
|
||||
|
||||
root = pathlib.Path(__file__).parent / ".."
|
||||
icons = root / "assets/icons"
|
||||
|
||||
|
||||
def count_icon_usages(name: str):
|
||||
count = 0
|
||||
name = name.encode()
|
||||
# EXTREMELY wasteful, but who cares
|
||||
for dir in ("applications", "furi", "lib", "targets"):
|
||||
for filetype in (".c", ".cpp", ".h", ".fam"):
|
||||
for file in (root / dir).glob(f"**/*{filetype}"):
|
||||
try:
|
||||
if name in file.read_bytes():
|
||||
count += 1
|
||||
except Exception:
|
||||
print(f"Failed to read {file}")
|
||||
return count
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
counts = {}
|
||||
|
||||
for category in icons.iterdir():
|
||||
if not category.is_dir():
|
||||
continue
|
||||
for icon in category.iterdir():
|
||||
if icon.is_dir() and (icon / "frame_rate").is_file():
|
||||
name = "A_" + icon.name.replace("-", "_")
|
||||
elif icon.is_file() and icon.suffix == ".png":
|
||||
name = "I_" + "_".join(icon.name.split(".")[:-1]).replace("-", "_")
|
||||
else:
|
||||
continue
|
||||
counts[name[2:]] = count_icon_usages(name)
|
||||
|
||||
for name, count in sorted(counts.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f"{name} used {count} times")
|
||||
@@ -181,7 +181,15 @@ def _proto_ver_generator(target, source, env):
|
||||
file.write("\n".join(version_file_data))
|
||||
|
||||
|
||||
def CompileIcons(env, target_dir, source_dir, *, icon_bundle_name="assets_icons"):
|
||||
def CompileIcons(
|
||||
env,
|
||||
target_dir,
|
||||
source_dir,
|
||||
*,
|
||||
icon_bundle_name="assets_icons",
|
||||
fw_bundle=False,
|
||||
add_include=False,
|
||||
):
|
||||
try:
|
||||
os.mkdir(str(source_dir))
|
||||
except FileExistsError:
|
||||
@@ -191,6 +199,8 @@ def CompileIcons(env, target_dir, source_dir, *, icon_bundle_name="assets_icons"
|
||||
None,
|
||||
ICON_SRC_DIR=source_dir,
|
||||
ICON_FILE_NAME=icon_bundle_name,
|
||||
ICON_FW_BUNDLE=int(fw_bundle),
|
||||
ICON_ADD_INCLUDE=int(add_include),
|
||||
)
|
||||
|
||||
|
||||
@@ -223,6 +233,10 @@ def generate(env):
|
||||
"${TARGET.dir}",
|
||||
"--filename",
|
||||
"${ICON_FILE_NAME}",
|
||||
"--fw-bundle",
|
||||
"${ICON_FW_BUNDLE}",
|
||||
"--add-include",
|
||||
"${ICON_ADD_INCLUDE}",
|
||||
],
|
||||
],
|
||||
"${ICONSCOMSTR}",
|
||||
|
||||
@@ -90,6 +90,7 @@ class AppBuilder:
|
||||
self.app_work_dir,
|
||||
self.app._appdir.Dir(self.app.fap_icon_assets),
|
||||
icon_bundle_name=f"{self.app.fap_icon_assets_symbol or self.app.appid }_icons",
|
||||
add_include=True,
|
||||
)
|
||||
self.app_env.Alias("_fap_icons", fap_icons)
|
||||
self.fw_env.Append(_APP_ICONS=[fap_icons])
|
||||
|
||||
Reference in New Issue
Block a user