use std::path::{Path, PathBuf};
use lensfun::Database;
use lensfun::calib::{DistortionModel, TcaModel, VignettingModel};
use lensfun::db::{MAX_DATABASE_VERSION, MIN_DATABASE_VERSION};
use lensfun::lens::LensType;
fn data_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("data/db")
}
fn load_bundled_db() -> Database {
Database::load_dir(data_dir()).expect("bundled database loads cleanly")
}
#[test]
fn version_constants_match_upstream() {
assert_eq!(MIN_DATABASE_VERSION, 0);
assert_eq!(MAX_DATABASE_VERSION, 2);
}
#[test]
fn load_bundled_database_parses() {
let db = load_bundled_db();
assert!(!db.mounts.is_empty(), "expected to load some mounts");
assert!(!db.cameras.is_empty(), "expected to load some cameras");
assert!(!db.lenses.is_empty(), "expected to load some lenses");
}
#[test]
fn load_str_handles_minimal_envelope() {
let mut db = Database::new();
db.load_str(
r#"<?xml version="1.0"?>
<lensdatabase version="2">
<mount><name>Test Mount</name></mount>
</lensdatabase>"#,
)
.unwrap();
assert_eq!(db.mounts.len(), 1);
assert_eq!(db.mounts[0].name, "Test Mount");
}
#[test]
fn rejects_unknown_root_element() {
let mut db = Database::new();
let err = db
.load_str(r#"<?xml version="1.0"?><root version="2"></root>"#)
.unwrap_err();
assert!(format!("{err}").contains("expected root element"));
}
#[test]
fn rejects_too_new_version() {
let mut db = Database::new();
let err = db
.load_str(r#"<?xml version="1.0"?><lensdatabase version="999"></lensdatabase>"#)
.unwrap_err();
assert!(format!("{err}").contains("999"));
}
#[test]
fn parses_localized_mount_names() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<mount>
<name>Canon EF</name>
<name lang="de">Canon EF</name>
<compat>Canon EF-S</compat>
<compat>Canon RF</compat>
</mount>
</lensdatabase>"#,
)
.unwrap();
let m = &db.mounts[0];
assert_eq!(m.name, "Canon EF");
assert_eq!(
m.names_localized.get("de").map(String::as_str),
Some("Canon EF")
);
assert_eq!(m.compat, vec!["Canon EF-S", "Canon RF"]);
}
#[test]
fn parses_camera_with_localized_strings() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<camera>
<maker>Pentax Corporation</maker>
<maker lang="en">Pentax</maker>
<model>Pentax K100D</model>
<model lang="en">K100D</model>
<mount>Pentax KAF</mount>
<cropfactor>1.531</cropfactor>
</camera>
</lensdatabase>"#,
)
.unwrap();
let cam = &db.cameras[0];
assert_eq!(cam.maker, "Pentax Corporation");
assert_eq!(
cam.maker_localized.get("en").map(String::as_str),
Some("Pentax")
);
assert_eq!(cam.model, "Pentax K100D");
assert_eq!(
cam.model_localized.get("en").map(String::as_str),
Some("K100D")
);
assert_eq!(cam.mount, "Pentax KAF");
assert!((cam.crop_factor - 1.531).abs() < 1e-6);
}
#[test]
fn rejects_camera_missing_required_field() {
let mut db = Database::new();
let err = db
.load_str(
r#"<lensdatabase version="2">
<camera>
<maker>Acme</maker>
<mount>Acme M</mount>
<cropfactor>1.5</cropfactor>
</camera>
</lensdatabase>"#,
)
.unwrap_err();
assert!(format!("{err}").contains("invalid camera"));
}
#[test]
fn parses_camera_aspect_ratio_fraction() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<camera>
<maker>Canon</maker>
<model>PowerShot</model>
<mount>Compact</mount>
<cropfactor>5.6</cropfactor>
<aspect-ratio>4:3</aspect-ratio>
</camera>
</lensdatabase>"#,
)
.unwrap();
let ar = db.cameras[0].aspect_ratio.unwrap();
assert!((ar - (4.0 / 3.0)).abs() < 1e-6);
}
#[test]
fn parses_lens_focal_aperture_value_form() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<lens>
<maker>Canon</maker>
<model>EF 50mm f/1.8</model>
<mount>Canon EF</mount>
<focal value="50"/>
<aperture min="1.8" max="22"/>
<cropfactor>1</cropfactor>
</lens>
</lensdatabase>"#,
)
.unwrap();
let lens = &db.lenses[0];
assert_eq!(lens.focal_min, 50.0);
assert_eq!(lens.focal_max, 50.0);
assert_eq!(lens.aperture_min, 1.8);
assert_eq!(lens.aperture_max, 22.0);
assert_eq!(lens.lens_type, LensType::Rectilinear);
}
#[test]
fn parses_lens_type_case_insensitive() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<lens>
<maker>Sigma</maker>
<model>8mm Fisheye</model>
<mount>Canon EF</mount>
<focal value="8"/>
<type>Fisheye</type>
<cropfactor>1</cropfactor>
</lens>
</lensdatabase>"#,
)
.unwrap();
assert_eq!(db.lenses[0].lens_type, LensType::FisheyeEquidistant);
}
#[test]
fn parses_distortion_models() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<lens>
<maker>X</maker>
<model>Test</model>
<mount>Test</mount>
<focal min="10" max="20"/>
<cropfactor>1.5</cropfactor>
<calibration>
<distortion model="poly3" focal="10" k1="-0.02"/>
<distortion model="poly5" focal="15" k1="0.01" k2="0.005"/>
<distortion model="ptlens" focal="20" a="0.001" b="-0.002" c="0.003"/>
</calibration>
</lens>
</lensdatabase>"#,
)
.unwrap();
let lens = &db.lenses[0];
assert_eq!(lens.calib_distortion.len(), 3);
assert!(matches!(
lens.calib_distortion[0].model,
DistortionModel::Poly3 { k1 } if (k1 + 0.02).abs() < 1e-6
));
assert!(matches!(
lens.calib_distortion[1].model,
DistortionModel::Poly5 { k1, k2 }
if (k1 - 0.01).abs() < 1e-6 && (k2 - 0.005).abs() < 1e-6
));
assert!(matches!(
lens.calib_distortion[2].model,
DistortionModel::Ptlens { a, b, c }
if (a - 0.001).abs() < 1e-6 && (b + 0.002).abs() < 1e-6 && (c - 0.003).abs() < 1e-6
));
let rf0 = lens.calib_distortion[0].real_focal.unwrap();
assert!((rf0 - 10.0 * (1.0 - (-0.02))).abs() < 1e-5);
}
#[test]
fn parses_tca_linear_and_poly3() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<lens>
<maker>X</maker>
<model>Test</model>
<mount>Test</mount>
<focal min="35" max="35"/>
<cropfactor>1.5</cropfactor>
<calibration>
<tca model="linear" focal="35" kr="1.0003" kb="1.0007"/>
<tca model="poly3" focal="35" vr="1.0001" vb="1.0002"
cr="0.0003" cb="0.0004" br="-0.0001" bb="-0.0002"/>
</calibration>
</lens>
</lensdatabase>"#,
)
.unwrap();
let lens = &db.lenses[0];
assert_eq!(lens.calib_tca.len(), 2);
match lens.calib_tca[0].model {
TcaModel::Linear { kr, kb } => {
assert!((kr - 1.0003).abs() < 1e-6);
assert!((kb - 1.0007).abs() < 1e-6);
}
_ => panic!("expected Linear"),
}
match lens.calib_tca[1].model {
TcaModel::Poly3 { red, blue } => {
assert_eq!(red, [1.0001, 0.0003, -0.0001]);
assert_eq!(blue, [1.0002, 0.0004, -0.0002]);
}
_ => panic!("expected Poly3"),
}
}
#[test]
fn parses_vignetting_pa() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<lens>
<maker>X</maker>
<model>Test</model>
<mount>Test</mount>
<focal value="50"/>
<cropfactor>1.5</cropfactor>
<calibration>
<vignetting model="pa" focal="50" aperture="2.8" distance="10"
k1="-0.5" k2="0.3" k3="-0.1"/>
</calibration>
</lens>
</lensdatabase>"#,
)
.unwrap();
let v = &db.lenses[0].calib_vignetting[0];
assert_eq!(v.focal, 50.0);
assert_eq!(v.aperture, 2.8);
assert_eq!(v.distance, 10.0);
assert!(matches!(
v.model,
VignettingModel::Pa { k1, k2, k3 }
if (k1 + 0.5).abs() < 1e-6 && (k2 - 0.3).abs() < 1e-6 && (k3 + 0.1).abs() < 1e-6
));
}
#[test]
fn parses_lens_center_and_aspect_ratio() {
let mut db = Database::new();
db.load_str(
r#"<lensdatabase version="2">
<lens>
<maker>X</maker>
<model>Test</model>
<mount>Test</mount>
<focal value="50"/>
<center x="0.01" y="-0.02"/>
<cropfactor>2</cropfactor>
<aspect-ratio>16:9</aspect-ratio>
</lens>
</lensdatabase>"#,
)
.unwrap();
let lens = &db.lenses[0];
assert_eq!(lens.center_x, 0.01);
assert_eq!(lens.center_y, -0.02);
assert!((lens.aspect_ratio - (16.0 / 9.0)).abs() < 1e-6);
assert_eq!(lens.crop_factor, 2.0);
}
#[test]
fn db_cam_search() {
let db = load_bundled_db();
let cameras = db.find_cameras(Some("pentax"), "K100D");
assert!(!cameras.is_empty());
assert_eq!(cameras[0].model, "Pentax K100D");
let cameras = db.find_cameras(None, "K 100 D");
assert!(!cameras.is_empty());
assert_eq!(cameras[0].model, "Pentax K100D");
let cameras = db.find_cameras(None, "PentAX K100 D");
assert!(!cameras.is_empty());
assert_eq!(cameras[0].model, "Pentax K100D");
}
#[test]
fn db_lens_search() {
let db = load_bundled_db();
let lenses = db.find_lenses(None, "pEntax 50-200 ED");
assert!(!lenses.is_empty());
assert_eq!(lenses[0].model, "smc Pentax-DA 50-200mm f/4-5.6 DA ED");
let lenses = db.find_lenses(None, "smc Pentax-DA 50-200mm f/4-5.6 DA ED");
assert!(!lenses.is_empty());
assert_eq!(lenses[0].model, "smc Pentax-DA 50-200mm f/4-5.6 DA ED");
let lenses = db.find_lenses(None, "PENTAX fa 28mm 2.8");
assert!(!lenses.is_empty());
assert_eq!(lenses[0].model, "smc Pentax-FA 28mm f/2.8 AL");
let cameras = db.find_cameras(Some("Ricoh"), "k-70");
assert!(!cameras.is_empty());
let lenses = db.find_lenses(Some(cameras[0]), "Fotasy M3517 35mm");
assert!(!lenses.is_empty());
assert_eq!(lenses[0].model, "Fotasy M3517 35mm f/1.7");
}