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 return Err("Coordinates out of bounds in lookup table");
86 }
87
88 let idx = ((py * self.xsize + px) * 2) as usize;
89 if idx + 1 >= self.o2i.len() {
90 return Err("Lookup table not properly initialized");
91 }
92
93 let ix = self.o2i[idx];
94 let iy = self.o2i[idx + 1];
95
96 Ok((ix, iy))
97 }
98
99 /// Applies inverse distortion lookup (calibrated to measured)
100 pub fn ideal2observ(&self, ix: f32, iy: f32) -> Result<(f32, f32), &'static str> {
101 let px = (ix + 0.5) as i32 + self.x_off;
102 let py = (iy + 0.5) as i32 + self.y_off;
103
104 if px < 0 || px >= self.xsize || py < 0 || py >= self.ysize {
105 return Err("Coordinates out of bounds in lookup table");
106 }
107
108 let idx = ((py * self.xsize + px) * 2) as usize;
109 if idx + 1 >= self.i2o.len() {
110 return Err("Lookup table not properly initialized");
111 }
112
113 let ox = self.i2o[idx];
114 let oy = self.i2o[idx + 1];
115
116 Ok((ox, oy))
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use std::io::Cursor;
124
125 #[test]
126 fn test_arparam_load_big_endian() {
127 // Construct a dummy byte array representing BigEndian encoded param data
128 // xsize (4), ysize(4), mat(3*4 * 8), dist_factor(9 * 8)
129
130 let mut buffer = Vec::new();
131 // xsize = 640
132 buffer.extend_from_slice(&640i32.to_be_bytes());
133 // ysize = 480
134 buffer.extend_from_slice(&480i32.to_be_bytes());
135
136 // Fill mat with 1.0
137 for _ in 0..12 {
138 buffer.extend_from_slice(&1.0f64.to_be_bytes());
139 }
140
141 // Fill dist_factor with 2.0
142 for _ in 0..9 {
143 buffer.extend_from_slice(&2.0f64.to_be_bytes());
144 }
145
146 let cursor = Cursor::new(buffer);
147 let param = ARParam::load(cursor).expect("Failed to load params");
148
149 assert_eq!(param.xsize, 640);
150 assert_eq!(param.ysize, 480);
151 assert_eq!(param.mat[0][0], 1.0);
152 assert_eq!(param.dist_factor[0], 2.0);
153 }
154
155 #[test]
156 fn test_arparam_finalize_version_1() {
157 let mut param = ARParam::default();
158 param.dist_factor[2] = 10.0;
159 param.dist_factor[3] = 20.0;
160
161 param.finalize_version_1();
162
163 assert_eq!(param.dist_factor[2], 20.0);
164 assert_eq!(param.dist_factor[3], 10.0);
165 }
166}