webarkitlib_rs/param.rs
1/*
2 * param.rs
3 * WebARKitLib-rs
4 *
5 * This file is part of WebARKitLib-rs - WebARKit.
6 *
7 * WebARKitLib-rs is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * WebARKitLib-rs is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with WebARKitLib-rs. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * As a special exception, the copyright holders of this library give you
21 * permission to link this library with independent modules to produce an
22 * executable, regardless of the license terms of these independent modules, and to
23 * copy and distribute the resulting executable under terms of your choice,
24 * provided that you also meet, for each linked independent module, the terms and
25 * conditions of the license of that module. An independent module is a module
26 * which is neither derived from nor based on this library. If you modify this
27 * library, you may extend this exception to your version of the library, but you
28 * are not obligated to do so. If you do not wish to do so, delete this exception
29 * statement from your version.
30 *
31 * Copyright 2026 WebARKit.
32 *
33 * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt
34 *
35 */
36
37//! Parameter loading and manipulation utilities
38//! Translated from ARToolKit C headers (param.h)
39
40use byteorder::{BigEndian, ReadBytesExt};
41use std::io::{self, Read};
42use crate::types::ARParam;
43
44impl ARParam {
45 /// Load ARParam from a byte stream (Endian-safe cross-platform BigEndian deserialization)
46 pub fn load<R: Read>(mut reader: R) -> io::Result<Self> {
47 let mut param = ARParam::default();
48
49 // ARToolKit 5 parameter files are encoded in BigEndian format.
50 param.xsize = reader.read_i32::<BigEndian>()?;
51 param.ysize = reader.read_i32::<BigEndian>()?;
52
53 // Load 3x4 projection matrix
54 for row in 0..3 {
55 for col in 0..4 {
56 param.mat[row][col] = reader.read_f64::<BigEndian>()?;
57 }
58 }
59
60 // Load distortion factors. AR_DIST_FACTOR_NUM_MAX is 9.
61 for i in 0..crate::types::AR_DIST_FACTOR_NUM_MAX {
62 if let Ok(val) = reader.read_f64::<BigEndian>() {
63 param.dist_factor[i] = val;
64 } else {
65 break; // End of file or buffer handled gracefully for older parameter files.
66 }
67 }
68
69 Ok(param)
70 }
71
72 /// Legacy ARToolKit v1 specific finalizer (swaps dist_factor 2 and 3)
73 pub fn finalize_version_1(&mut self) {
74 self.dist_factor.swap(2, 3);
75 }
76}
77
78impl crate::types::ARParamLTf {
79 /// Applies 2D distortion correction lookup
80 pub fn observ2ideal(&self, ox: f32, oy: f32) -> Result<(f32, f32), &'static str> {
81 let px = (ox + 0.5) as i32 + self.x_off;
82 let py = (oy + 0.5) as i32 + self.y_off;
83
84 if px < 0 || px >= self.xsize || py < 0 || py >= self.ysize {
85 println!("param.rs observ2ideal bounds fail: ox={}, oy={}, px={}, py={}, xsize={}, ysize={}", ox, oy, px, py, self.xsize, self.ysize);
86 return Err("Coordinates out of bounds in lookup table");
87 }
88
89 let idx = ((py * self.xsize + px) * 2) as usize;
90 if idx + 1 >= self.o2i.len() {
91 return Err("Lookup table not properly initialized");
92 }
93
94 let ix = self.o2i[idx];
95 let iy = self.o2i[idx + 1];
96
97 Ok((ix, iy))
98 }
99
100 /// Applies inverse distortion lookup (calibrated to measured)
101 pub fn ideal2observ(&self, ix: f32, iy: f32) -> Result<(f32, f32), &'static str> {
102 let px = (ix + 0.5) as i32 + self.x_off;
103 let py = (iy + 0.5) as i32 + self.y_off;
104
105 if px < 0 || px >= self.xsize || py < 0 || py >= self.ysize {
106 return Err("Coordinates out of bounds in lookup table");
107 }
108
109 let idx = ((py * self.xsize + px) * 2) as usize;
110 if idx + 1 >= self.i2o.len() {
111 return Err("Lookup table not properly initialized");
112 }
113
114 let ox = self.i2o[idx];
115 let oy = self.i2o[idx + 1];
116
117 Ok((ox, oy))
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use std::io::Cursor;
125
126 #[test]
127 fn test_arparam_load_big_endian() {
128 // Construct a dummy byte array representing BigEndian encoded param data
129 // xsize (4), ysize(4), mat(3*4 * 8), dist_factor(9 * 8)
130
131 let mut buffer = Vec::new();
132 // xsize = 640
133 buffer.extend_from_slice(&640i32.to_be_bytes());
134 // ysize = 480
135 buffer.extend_from_slice(&480i32.to_be_bytes());
136
137 // Fill mat with 1.0
138 for _ in 0..12 {
139 buffer.extend_from_slice(&1.0f64.to_be_bytes());
140 }
141
142 // Fill dist_factor with 2.0
143 for _ in 0..9 {
144 buffer.extend_from_slice(&2.0f64.to_be_bytes());
145 }
146
147 let cursor = Cursor::new(buffer);
148 let param = ARParam::load(cursor).expect("Failed to load params");
149
150 assert_eq!(param.xsize, 640);
151 assert_eq!(param.ysize, 480);
152 assert_eq!(param.mat[0][0], 1.0);
153 assert_eq!(param.dist_factor[0], 2.0);
154 }
155
156 #[test]
157 fn test_arparam_finalize_version_1() {
158 let mut param = ARParam::default();
159 param.dist_factor[2] = 10.0;
160 param.dist_factor[3] = 20.0;
161
162 param.finalize_version_1();
163
164 assert_eq!(param.dist_factor[2], 20.0);
165 assert_eq!(param.dist_factor[3], 10.0);
166 }
167}