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            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}