Skip to main content

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}