use std::path::PathBuf;
pub struct ImageConfig {
pub lv_conf_dir: PathBuf,
pub lvgl_include_dir: PathBuf,
pub converter: PathBuf,
}
impl ImageConfig {
pub fn from_env() -> Self {
let lv_conf_dir = PathBuf::from(
std::env::var("DEP_LV_CONFIG_PATH")
.expect("DEP_LV_CONFIG_PATH must be set (points to dir containing lv_conf.h)"),
);
let lvgl_root = find_lvgl_root();
ImageConfig {
lv_conf_dir,
lvgl_include_dir: lvgl_root.join("src"),
converter: lvgl_root.join("scripts/LVGLImage.py"),
}
}
pub fn image_asset(&self, name: &str, png_path: &str) {
let manifest_dir =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set"));
let png_abs = manifest_dir.join(png_path);
assert!(
png_abs.exists(),
"image asset not found: {}",
png_abs.display()
);
let cf = color_format_from_conf(&self.lv_conf_dir);
let status = std::process::Command::new("python3")
.arg(&self.converter)
.args(["--ofmt", "C"])
.args(["--cf", cf])
.args(["--align", "1"])
.args(["--name", name])
.args(["-o", out_dir.to_str().unwrap()])
.arg(&png_abs)
.status()
.unwrap_or_else(|e| panic!("failed to run LVGLImage.py: {e}"));
assert!(
status.success(),
"LVGLImage.py failed with exit code {:?}",
status.code()
);
let c_file = out_dir.join(format!("{name}.c"));
assert!(
c_file.exists(),
"LVGLImage.py did not produce {}",
c_file.display()
);
cc::Build::new()
.file(&c_file)
.define("LV_LVGL_H_INCLUDE_SIMPLE", None)
.include(&self.lvgl_include_dir)
.include(&self.lv_conf_dir)
.opt_level(2)
.compile(&format!("lvgl_img_{name}"));
println!("cargo:rerun-if-changed={png_path}");
}
}
fn find_lvgl_root() -> PathBuf {
if let Ok(dir) = std::env::var("DEP_LV_SRC_DIR") {
let p = PathBuf::from(dir);
if p.join("lv_version.h").exists() {
return p;
}
}
let manifest_dir =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
for base in [
manifest_dir.as_path(),
manifest_dir.parent().unwrap_or(&manifest_dir),
] {
let candidate = base.join("oxivgl-sys").join("lvgl");
if candidate.join("lv_version.h").exists() {
return candidate;
}
}
let cargo_home = std::env::var("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| {
PathBuf::from(std::env::var("HOME").unwrap_or_default()).join(".cargo")
});
let checkouts = cargo_home.join("git/checkouts");
if let Ok(entries) = std::fs::read_dir(&checkouts) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with("oxivgl_sys-") || name.starts_with("oxivgl_sys-") {
if let Ok(revs) = std::fs::read_dir(entry.path()) {
for rev in revs.flatten() {
let candidate = rev.path().join("lvgl");
if candidate.join("lv_version.h").exists() {
return candidate;
}
}
}
}
}
}
let manifest_dir =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
let fallback = manifest_dir.join("thirdparty/oxivgl_sys/lvgl");
if fallback.join("lv_version.h").exists() {
return fallback;
}
panic!(
"LVGL source tree not found in oxivgl-sys/lvgl/, \
{}/git/checkouts/{{oxivgl_sys,oxivgl_sys}}-*/*/lvgl/, \
or thirdparty/oxivgl_sys/lvgl/",
cargo_home.display()
);
}
fn color_format_from_conf(lv_conf_dir: &std::path::Path) -> &'static str {
let conf_path = lv_conf_dir.join("lv_conf.h");
let contents = std::fs::read_to_string(&conf_path)
.unwrap_or_else(|e| panic!("cannot read {}: {e}", conf_path.display()));
for line in contents.lines() {
let line = line.trim();
if line.starts_with("#define") && line.contains("LV_COLOR_DEPTH") {
if let Some(val) = line.split_whitespace().nth(2) {
return match val {
"16" => "RGB565",
"24" => "RGB888",
"32" => "ARGB8888",
other => panic!(
"unsupported LV_COLOR_DEPTH {other} in {} (expected 16, 24, or 32)",
conf_path.display()
),
};
}
}
}
panic!("LV_COLOR_DEPTH not found in {}", conf_path.display());
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn parse_color_depth_16() {
let dir = std::env::temp_dir().join("oxivgl_build_test_16");
std::fs::create_dir_all(&dir).unwrap();
let mut f = std::fs::File::create(dir.join("lv_conf.h")).unwrap();
writeln!(f, "#define LV_COLOR_DEPTH 16").unwrap();
assert_eq!(color_format_from_conf(&dir), "RGB565");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn parse_color_depth_32() {
let dir = std::env::temp_dir().join("oxivgl_build_test_32");
std::fs::create_dir_all(&dir).unwrap();
let mut f = std::fs::File::create(dir.join("lv_conf.h")).unwrap();
writeln!(f, "#define LV_COLOR_DEPTH 32").unwrap();
assert_eq!(color_format_from_conf(&dir), "ARGB8888");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
#[should_panic(expected = "unsupported LV_COLOR_DEPTH")]
fn parse_color_depth_unsupported() {
let dir = std::env::temp_dir().join("oxivgl_build_test_bad");
std::fs::create_dir_all(&dir).unwrap();
let mut f = std::fs::File::create(dir.join("lv_conf.h")).unwrap();
writeln!(f, "#define LV_COLOR_DEPTH 8").unwrap();
color_format_from_conf(&dir);
}
}