Skip to main content

webarkitlib_rs/
marker.rs

1/*
2 *  marker.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//! Marker Detection Pipeline
38//! Ported from arDetectMarker.c, arDetectMarker2.c, and arGetMarkerInfo.c
39
40use crate::types::{ARLabelInfo, ARMarkerInfo2, ARdouble};
41use log::debug;
42
43pub const AR_AREA_MAX: i32 = 100000;
44pub const AR_AREA_MIN: i32 = 70;
45pub const AR_SQUARE_FIT_THRESH: f64 = 0.05;
46pub const AR_CHAIN_MAX: usize = 10000;
47pub const AR_SQUARE_MAX: usize = 30;
48
49#[derive(Debug, PartialEq, Clone, Copy)]
50pub enum ImageProcMode {
51    FrameImage = 0,
52    FieldImage = 1,
53}
54
55/// High-level marker detection pipeline ported from arDetectMarker.
56/// This method handles thresholding, region extraction, subpixel refinement, and template matching.
57pub fn ar_detect_marker(
58    ar_handle: &mut crate::types::ARHandle,
59    frame: &crate::types::AR2VideoBufferT,
60) -> Result<(), &'static str> {
61    ar_handle.marker_num = 0;
62    
63    let luma_buff = match &frame.buff_luma {
64        Some(b) => b.as_slice(),
65        None => return Err("AR2VideoBufferT requires buff_luma to be available"),
66    };
67    
68    let color_buff = match &frame.buff {
69        Some(b) => b.as_slice(),
70        None => return Err("AR2VideoBufferT requires buff to be available"),
71    };
72    
73    let thresh = ar_handle.ar_labeling_thresh as u8;
74    let label_mode = if ar_handle.ar_labeling_mode == 0 {
75        crate::labeling::LabelingMode::BlackRegion
76    } else {
77        crate::labeling::LabelingMode::WhiteRegion
78    };
79    
80    let labeling_proc_mode = if ar_handle.ar_image_proc_mode == 0 {
81        crate::labeling::ImageProcMode::FrameImage
82    } else {
83        crate::labeling::ImageProcMode::FieldImage
84    };
85
86    crate::labeling::ar_labeling(
87        luma_buff,
88        ar_handle.xsize,
89        ar_handle.ysize,
90        label_mode,
91        thresh,
92        labeling_proc_mode,
93        &mut ar_handle.label_info,
94        ar_handle.ar_debug != 0
95    )?;
96    
97    if ar_handle.ar_debug != 0 {
98        debug!("ar_labeling found {} labels.", ar_handle.label_info.label_num);
99    }
100
101    let image_proc_mode = if ar_handle.ar_image_proc_mode == 0 {
102        ImageProcMode::FrameImage
103    } else {
104        ImageProcMode::FieldImage
105    };
106
107    ar_detect_marker2(
108        ar_handle.xsize,
109        ar_handle.ysize,
110        &mut ar_handle.label_info,
111        image_proc_mode,
112        AR_AREA_MAX,
113        AR_AREA_MIN,
114        AR_SQUARE_FIT_THRESH,
115        &mut *ar_handle.marker_info2,
116        &mut ar_handle.marker2_num,
117    )?;
118
119    if ar_handle.ar_debug != 0 {
120        debug!("ar_detect_marker2 found {} square candidates.", ar_handle.marker2_num);
121    }
122
123    if ar_handle.ar_param_lt.is_null() {
124        return Err("ARParamLT is null in ARHandle");
125    }
126    
127    let image_proc_mode2 = if ar_handle.ar_image_proc_mode == 0 {
128        ImageProcMode::FrameImage
129    } else {
130        ImageProcMode::FieldImage
131    };
132    
133    let param_ltf = unsafe { &(*ar_handle.ar_param_lt).param_ltf };
134
135    let patt_handle_opt = if !ar_handle.patt_handle.is_null() {
136        Some(unsafe { &*ar_handle.patt_handle })
137    } else {
138        None
139    };
140
141    ar_get_marker_info(
142        color_buff,
143        ar_handle.xsize,
144        ar_handle.ysize,
145        ar_handle.ar_pixel_format,
146        &ar_handle.marker_info2[..],
147        ar_handle.marker2_num,
148        image_proc_mode2,
149        ar_handle.ar_pattern_detection_mode,
150        param_ltf,
151        ar_handle.patt_ratio,
152        patt_handle_opt,
153        &mut *ar_handle.marker_info,
154        &mut ar_handle.marker_num,
155        ar_handle.matrix_code_type,
156    )?;
157
158    if ar_handle.ar_debug != 0 {
159        debug!("ar_get_marker_info produced {} final markers.", ar_handle.marker_num);
160    }
161
162    Ok(())
163}
164
165/// Ported from arDetectMarker2 in arDetectMarker2.c
166pub fn ar_detect_marker2(
167    xsize: i32,
168    ysize: i32,
169    label_info: &mut ARLabelInfo,
170    image_proc_mode: ImageProcMode,
171    area_max: i32,
172    area_min: i32,
173    square_fit_thresh: ARdouble,
174    marker_info2: &mut [ARMarkerInfo2],
175    marker2_num: &mut i32,
176) -> Result<(), &'static str> {
177    let mut xsize_local = xsize;
178    let mut ysize_local = ysize;
179    let mut area_min_local = area_min;
180    let mut area_max_local = area_max;
181
182    if matches!(image_proc_mode, ImageProcMode::FieldImage) {
183        area_min_local /= 4;
184        area_max_local /= 4;
185        xsize_local /= 2;
186        ysize_local /= 2;
187    }
188
189    *marker2_num = 0;
190    
191    let label_num = label_info.label_num as usize;
192    for i in 0..label_num {
193        if label_info.area[i] < area_min_local || label_info.area[i] > area_max_local {
194            debug!("Label {} skipped due to Area ({}) not in [{}, {}]", i, label_info.area[i], area_min_local, area_max_local); 
195            continue;
196        }
197        if label_info.clip[i][0] <= 1 || label_info.clip[i][1] >= xsize_local - 2 {
198            debug!("Label {} skipped due to X-Clip bounds", i);
199            continue;
200        }
201        if label_info.clip[i][2] <= 1 || label_info.clip[i][3] >= ysize_local - 2 {
202            debug!("Label {} skipped due to Y-Clip bounds", i);
203            continue;
204        }
205
206        let mut current_marker = ARMarkerInfo2::default();
207        
208        let ret = ar_get_contour(
209            &label_info.label_image,
210            xsize_local,
211            ysize_local,
212            &label_info.work,
213            (i + 1) as i32,
214            &label_info.clip[i],
215            &mut current_marker,
216        );
217        
218        if ret.is_err() {
219            debug!("ar_get_contour failed for label {}: {:?}", i, ret.unwrap_err());
220            continue;
221        }
222
223        let ret = check_square(label_info.area[i], &mut current_marker, square_fit_thresh);
224        if ret.is_err() {
225            debug!("check_square failed for label {}: {:?}", i, ret.unwrap_err());
226            continue;
227        }
228
229        current_marker.area = label_info.area[i];
230        current_marker.pos[0] = label_info.pos[i][0];
231        current_marker.pos[1] = label_info.pos[i][1];
232        
233        marker_info2[*marker2_num as usize] = current_marker;
234        *marker2_num += 1;
235        if *marker2_num as usize == marker_info2.len() {
236            break;
237        }
238    }
239
240    // Sort/Filter identical overlapping markers
241    let num_markers = *marker2_num as usize;
242    for i in 0..num_markers {
243        for j in i + 1..num_markers {
244            if marker_info2[i].area == 0 || marker_info2[j].area == 0 {
245                continue;
246            }
247            let d = (marker_info2[i].pos[0] - marker_info2[j].pos[0]).powi(2)
248                  + (marker_info2[i].pos[1] - marker_info2[j].pos[1]).powi(2);
249            
250            if marker_info2[i].area > marker_info2[j].area {
251                if d < (marker_info2[i].area as ARdouble) / 4.0 {
252                    marker_info2[j].area = 0;
253                }
254            } else {
255                if d < (marker_info2[j].area as ARdouble) / 4.0 {
256                    marker_info2[i].area = 0;
257                }
258            }
259        }
260    }
261
262    // Compact the array
263    let mut valid_count = 0;
264    for i in 0..num_markers {
265        if marker_info2[i].area > 0 {
266            if i != valid_count {
267                marker_info2[valid_count] = marker_info2[i].clone();
268            }
269            valid_count += 1;
270        }
271    }
272    *marker2_num = valid_count as i32;
273
274    if matches!(image_proc_mode, ImageProcMode::FieldImage) {
275        for i in 0..(*marker2_num as usize) {
276            let pm = &mut marker_info2[i];
277            pm.area *= 4;
278            pm.pos[0] *= 2.0;
279            pm.pos[1] *= 2.0;
280            for j in 0..pm.coord_num as usize {
281                pm.x_coord[j] *= 2;
282                pm.y_coord[j] *= 2;
283            }
284        }
285    }
286
287    Ok(())
288}
289
290fn ar_get_contour(
291    limage: &[crate::types::ARLabelingLabelType],
292    xsize: i32,
293    _ysize: i32,
294    label_ref: &[i32],
295    label: i32,
296    clip: &[i32; 4],
297    marker_info2: &mut ARMarkerInfo2,
298) -> Result<(), &'static str> {
299    let xdir = [0, 1, 1, 1, 0, -1, -1, -1];
300    let ydir = [-1, -1, 0, 1, 1, 1, 0, -1];
301    
302    let mut sx = -1;
303    let sy = clip[2];
304    
305    let mut p_idx = (sy * xsize + clip[0]) as usize;
306    for i in clip[0]..=clip[1] {
307        if p_idx < limage.len() {
308            let val = limage[p_idx];
309            if val > 0 && label_ref[(val - 1) as usize] == label {
310                sx = i;
311                break;
312            }
313        }
314        p_idx += 1;
315    }
316    
317    if sx == -1 {
318        let mut row_dump = String::new();
319        let mut p_idx_d = (sy * xsize + clip[0]) as usize;
320        for _ in clip[0]..=clip[1] {
321            if p_idx_d < limage.len() {
322                let v = limage[p_idx_d];
323                if v > 0 {
324                    row_dump.push_str(&format!("{}({}),", v, label_ref[(v - 1) as usize]));
325                }
326            }
327            p_idx_d += 1;
328        }
329        debug!("ar_get_contour failed. label={}. clip={:?}. Found on row: {}", label, clip, row_dump);
330        return Err("Contour start point not found");
331    }
332
333    marker_info2.coord_num = 1;
334    marker_info2.x_coord[0] = sx;
335    marker_info2.y_coord[0] = sy;
336    let mut dir = 5;
337    
338    loop {
339        let last_idx = (marker_info2.coord_num - 1) as usize;
340        let p_idx = (marker_info2.y_coord[last_idx] * xsize + marker_info2.x_coord[last_idx]) as usize;
341        
342        dir = (dir + 5) % 8;
343        let mut found = false;
344        for _ in 0..8 {
345            let next_idx = (p_idx as isize + ydir[dir] as isize * xsize as isize + xdir[dir] as isize) as usize;
346            if next_idx < limage.len() && limage[next_idx] > 0 {
347                found = true;
348                break;
349            }
350            dir = (dir + 1) % 8;
351        }
352        
353        if !found {
354            return Err("Contour broken");
355        }
356        
357        let curr_idx = marker_info2.coord_num as usize;
358        marker_info2.x_coord[curr_idx] = marker_info2.x_coord[last_idx] + xdir[dir];
359        marker_info2.y_coord[curr_idx] = marker_info2.y_coord[last_idx] + ydir[dir];
360        
361        if marker_info2.x_coord[curr_idx] == sx && marker_info2.y_coord[curr_idx] == sy {
362            break;
363        }
364        
365        marker_info2.coord_num += 1;
366        if marker_info2.coord_num as usize >= AR_CHAIN_MAX - 1 {
367            return Err("Contour too long");
368        }
369    }
370
371    let mut dmax = 0;
372    let mut v1 = 0;
373    
374    for i in 1..marker_info2.coord_num as usize {
375        let d = (marker_info2.x_coord[i] - sx).pow(2) + (marker_info2.y_coord[i] - sy).pow(2);
376        if d > dmax {
377            dmax = d;
378            v1 = i;
379        }
380    }
381
382    let mut wx = vec![0; v1];
383    let mut wy = vec![0; v1];
384    
385    for i in 0..v1 {
386        wx[i] = marker_info2.x_coord[i];
387        wy[i] = marker_info2.y_coord[i];
388    }
389    
390    let coord_num = marker_info2.coord_num as usize;
391    for i in v1..coord_num {
392        marker_info2.x_coord[i - v1] = marker_info2.x_coord[i];
393        marker_info2.y_coord[i - v1] = marker_info2.y_coord[i];
394    }
395    
396    let offset = coord_num - v1;
397    for i in 0..v1 {
398        marker_info2.x_coord[offset + i] = wx[i];
399        marker_info2.y_coord[offset + i] = wy[i];
400    }
401    
402    let end_idx = marker_info2.coord_num as usize;
403    marker_info2.x_coord[end_idx] = marker_info2.x_coord[0];
404    marker_info2.y_coord[end_idx] = marker_info2.y_coord[0];
405    marker_info2.coord_num += 1;
406
407    Ok(())
408}
409
410fn check_square(area: i32, marker_info2: &mut ARMarkerInfo2, factor: ARdouble) -> Result<(), &'static str> {
411    let mut dmax = 0;
412    let mut v1 = 0;
413    let sx = marker_info2.x_coord[0];
414    let sy = marker_info2.y_coord[0];
415    let coord_num = marker_info2.coord_num as usize;
416    
417    for i in 1..(coord_num - 1) {
418        let d = (marker_info2.x_coord[i] - sx).pow(2) + (marker_info2.y_coord[i] - sy).pow(2);
419        if d > dmax {
420            dmax = d;
421            v1 = i;
422        }
423    }
424
425    let thresh = ((area as f64) / 0.75) * 0.01 * factor;
426    let mut vertex = [0; 10];
427    vertex[0] = 0;
428    let mut wv1 = [0; 10];
429    let mut wvnum1 = 0;
430    let mut wv2 = [0; 10];
431    let mut wvnum2 = 0;
432    
433    if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, 0, v1, thresh, &mut wv1, &mut wvnum1).is_err() {
434        return Err("Square check failed");
435    }
436    if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v1, coord_num - 1, thresh, &mut wv2, &mut wvnum2).is_err() {
437        return Err("Square check failed");
438    }
439
440    if wvnum1 == 1 && wvnum2 == 1 {
441        vertex[1] = wv1[0];
442        vertex[2] = v1;
443        vertex[3] = wv2[0];
444    } else if wvnum1 > 1 && wvnum2 == 0 {
445        let v2 = v1 / 2;
446        wvnum1 = 0;
447        wvnum2 = 0;
448        if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, 0, v2, thresh, &mut wv1, &mut wvnum1).is_err() {
449            return Err("Square check failed");
450        }
451        if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v2, v1, thresh, &mut wv2, &mut wvnum2).is_err() {
452            return Err("Square check failed");
453        }
454        if wvnum1 == 1 && wvnum2 == 1 {
455            vertex[1] = wv1[0];
456            vertex[2] = wv2[0];
457            vertex[3] = v1;
458        } else {
459            return Err("Not a square");
460        }
461    } else if wvnum1 == 0 && wvnum2 > 1 {
462        let v2 = (v1 + coord_num - 1) / 2;
463        wvnum1 = 0;
464        wvnum2 = 0;
465        if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v1, v2, thresh, &mut wv1, &mut wvnum1).is_err() {
466            return Err("Square check failed");
467        }
468        if get_vertex(&marker_info2.x_coord, &marker_info2.y_coord, v2, coord_num - 1, thresh, &mut wv2, &mut wvnum2).is_err() {
469            return Err("Square check failed");
470        }
471        if wvnum1 == 1 && wvnum2 == 1 {
472            vertex[1] = v1;
473            vertex[2] = wv1[0];
474            vertex[3] = wv2[0];
475        } else {
476            return Err("Not a square");
477        }
478    } else {
479        return Err("Not a square");
480    }
481
482    marker_info2.vertex[0] = vertex[0] as i32;
483    marker_info2.vertex[1] = vertex[1] as i32;
484    marker_info2.vertex[2] = vertex[2] as i32;
485    marker_info2.vertex[3] = vertex[3] as i32;
486    marker_info2.vertex[4] = (coord_num - 1) as i32;
487
488    Ok(())
489}
490
491fn get_vertex(
492    x_coord: &[i32],
493    y_coord: &[i32],
494    st: usize,
495    ed: usize,
496    thresh: ARdouble,
497    vertex: &mut [usize],
498    vnum: &mut usize,
499) -> Result<(), &'static str> {
500    let a = (y_coord[ed] - y_coord[st]) as f64;
501    let b = (x_coord[st] - x_coord[ed]) as f64;
502    let c = (x_coord[ed] * y_coord[st] - y_coord[ed] * x_coord[st]) as f64;
503    
504    let mut dmax = 0.0;
505    let mut v1 = st + 1;
506    
507    for i in (st + 1)..ed {
508        let d = a * (x_coord[i] as f64) + b * (y_coord[i] as f64) + c;
509        if d * d > dmax {
510            dmax = d * d;
511            v1 = i;
512        }
513    }
514    
515    if dmax / (a * a + b * b) > thresh {
516        if get_vertex(x_coord, y_coord, st, v1, thresh, vertex, vnum).is_err() {
517            return Err("Vertex expansion failed");
518        }
519        
520        if *vnum > 5 {
521            return Err("Too many vertices");
522        }
523        vertex[*vnum] = v1;
524        *vnum += 1;
525        
526        if get_vertex(x_coord, y_coord, v1, ed, thresh, vertex, vnum).is_err() {
527            return Err("Vertex expansion failed");
528        }
529    }
530    
531    Ok(())
532}
533
534use crate::math::{ARMat, ARVec};
535use crate::types::{ARParamLTf, ARMarkerInfo};
536
537/// Ports arGetLine from arGetLine.c
538pub fn ar_get_line(
539    x_coord: &[i32],
540    y_coord: &[i32],
541    _coord_num: usize,
542    vertex: &[i32],
543    param_ltf: &ARParamLTf,
544    line: &mut [[ARdouble; 3]; 4],
545    v: &mut [[ARdouble; 2]; 4],
546) -> Result<(), &'static str> {
547    for i in 0..4 {
548        let w1 = ((vertex[i + 1] - vertex[i] + 1) as f64) * 0.05 + 0.5;
549        let st = (vertex[i] as f64 + w1) as usize;
550        let ed = (vertex[i + 1] as f64 - w1) as usize;
551        let n = ed - st + 1;
552        
553        let mut input = ARMat::new(n as i32, 2);
554        for j in 0..n {
555            let (ix, iy) = param_ltf.observ2ideal(x_coord[st + j] as f32, y_coord[st + j] as f32)?;
556            input.m[j * 2 + 0] = ix as f64;
557            input.m[j * 2 + 1] = iy as f64;
558        }
559
560        let mut evec = ARMat::new(2, 2);
561        let mut ev = ARVec::new(2);
562        let mut mean = ARVec::new(2);
563
564        input.pca(&mut evec, &mut ev, &mut mean)?;
565        
566        line[i][0] = evec.m[1];
567        line[i][1] = -evec.m[0];
568        line[i][2] = -(line[i][0] * mean.v[0] + line[i][1] * mean.v[1]);
569    }
570
571    for i in 0..4 {
572        let w1 = line[(i + 3) % 4][0] * line[i][1] - line[i][0] * line[(i + 3) % 4][1];
573        if w1.abs() < 0.0001 {
574            return Err("Lines are near parallel");
575        }
576        v[i][0] = (line[(i + 3) % 4][1] * line[i][2] - line[i][1] * line[(i + 3) % 4][2]) / w1;
577        v[i][1] = (line[i][0] * line[(i + 3) % 4][2] - line[(i + 3) % 4][0] * line[i][2]) / w1;
578    }
579
580    Ok(())
581}
582
583/// Ports arGetMarkerInfo from arGetMarkerInfo.c
584pub fn ar_get_marker_info(
585    image: &[u8],
586    xsize: i32,
587    ysize: i32,
588    pixel_format: crate::types::ARPixelFormat,
589    marker_info2: &[ARMarkerInfo2],
590    marker2_num: i32,
591    image_proc_mode: ImageProcMode,
592    patt_detect_mode: i32,
593    param_ltf: &ARParamLTf,
594    patt_ratio: ARdouble,
595    patt_handle_opt: Option<&crate::types::ARPattHandle>,
596    marker_info: &mut [ARMarkerInfo],
597    marker_num: &mut i32,
598    _matrix_code_type: crate::types::ARMatrixCodeType,
599) -> Result<(), &'static str> {
600    let mut j = 0;
601    
602    for i in 0..marker2_num as usize {
603        marker_info[j].area = marker_info2[i].area;
604        
605        if let Ok((ix, iy)) = param_ltf.observ2ideal(marker_info2[i].pos[0] as f32, marker_info2[i].pos[1] as f32) {
606            marker_info[j].pos[0] = ix as f64;
607            marker_info[j].pos[1] = iy as f64;
608        } else {
609            continue;
610        }
611
612        if ar_get_line(
613            &marker_info2[i].x_coord,
614            &marker_info2[i].y_coord,
615            marker_info2[i].coord_num as usize,
616            &marker_info2[i].vertex,
617            param_ltf,
618            &mut marker_info[j].line,
619            &mut marker_info[j].vertex,
620        ).is_err() {
621            continue;
622        }
623
624        if let Some(patt_handle) = patt_handle_opt {
625            if patt_handle.patt_num > 0 {
626                let patt_size = patt_handle.patt_size;
627            let ext_patt_len = if patt_detect_mode == crate::pattern::AR_TEMPLATE_MATCHING_COLOR {
628                (patt_size * patt_size * 3) as usize
629            } else {
630                (patt_size * patt_size) as usize
631            };
632            let mut ext_patt = vec![0u8; ext_patt_len];
633            
634            let res = crate::pattern::ar_patt_get_image(
635                image_proc_mode as i32,
636                patt_detect_mode,
637                patt_size,
638                patt_size * 2, // Sample size factor (e.g. AR_PATT_SAMPLE_FACTOR1)
639                image,
640                xsize,
641                ysize,
642                pixel_format,
643                &marker_info[j].vertex,
644                patt_ratio,
645                &mut ext_patt,
646            );
647            
648            if res.is_ok() {
649                let mut p_code = -1;
650                let mut p_dir = 0;
651                let mut p_cf = -1.0;
652                let match_res = crate::pattern::pattern_match(
653                    patt_handle,
654                    patt_detect_mode,
655                    &ext_patt,
656                    patt_size,
657                    &mut p_code,
658                    &mut p_dir,
659                    &mut p_cf,
660                );
661                
662                if match_res.is_ok() && p_code >= 0 {
663                    marker_info[j].id = p_code;
664                    marker_info[j].dir = p_dir;
665                    marker_info[j].cf = p_cf;
666                } else {
667                    marker_info[j].id = -1;
668                    marker_info[j].dir = 0;
669                    marker_info[j].cf = p_cf;
670                }
671            } else {
672                marker_info[j].id = -1;
673                marker_info[j].dir = 0;
674                marker_info[j].cf = -1.0;
675            }
676        } else {
677            marker_info[j].id = -1;
678            marker_info[j].dir = 0;
679            marker_info[j].cf = 0.0;
680        }
681        } else {
682            marker_info[j].id = -1;
683            marker_info[j].dir = 0;
684            marker_info[j].cf = 0.0;
685        }
686
687        j += 1;
688    }
689    *marker_num = j as i32;
690
691    Ok(())
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697
698    #[test]
699    fn test_ar_detect_marker2_empty() {
700        let mut label_info = ARLabelInfo::default();
701        let mut marker_info2 = vec![ARMarkerInfo2::default(); AR_SQUARE_MAX];
702        let mut marker2_num = 0;
703
704        let res = ar_detect_marker2(
705            640,
706            480,
707            &mut label_info,
708            ImageProcMode::FrameImage,
709            AR_AREA_MAX,
710            AR_AREA_MIN,
711            AR_SQUARE_FIT_THRESH,
712            &mut marker_info2,
713            &mut marker2_num,
714        );
715
716        assert!(res.is_ok());
717        assert_eq!(marker2_num, 0);
718    }
719}