Skip to main content

docvortex_core/
image_numeric.rs

1//! 对独立图像字节执行数值操作,不访问 Python、PDFium 或 OpenCV。
2use std::collections::HashMap;
3
4/// 按声明位深从自有连续字节中读取像素。
5fn read(data: &[u8], index: usize, depth: u8) -> f32 {
6    match depth {
7        1 => data[index] as f32,
8        2 => u16::from_ne_bytes([data[index * 2], data[index * 2 + 1]]) as f32,
9        _ => f32::from_ne_bytes(data[index * 4..index * 4 + 4].try_into().unwrap()),
10    }
11}
12
13/// 目标位深使用饱和偶数舍入,浮点保持原始 IEEE 字节。
14fn write(output: &mut [u8], index: usize, value: f32, depth: u8) {
15    match depth {
16        1 => output[index] = value.round_ties_even().clamp(0., 255.) as u8,
17        2 => output[index * 2..index * 2 + 2]
18            .copy_from_slice(&(value.round_ties_even().clamp(0., 65535.) as u16).to_ne_bytes()),
19        _ => output[index * 4..index * 4 + 4].copy_from_slice(&value.to_ne_bytes()),
20    }
21}
22
23/// 分离卷积仅缓存当前需要的源行,避免创建全幅中间张量。
24#[allow(clippy::too_many_arguments)]
25pub fn resize(
26    data: &[u8],
27    width: usize,
28    height: usize,
29    channels: usize,
30    depth: u8,
31    target_w: usize,
32    target_h: usize,
33    mode: u8,
34    xindices: &[Vec<usize>],
35    xweights: &[Vec<f32>],
36    yindices: &[Vec<usize>],
37    yweights: &[Vec<f32>],
38) -> Result<Vec<u8>, String> {
39    if data.len()
40        != width
41            .checked_mul(height)
42            .and_then(|v| v.checked_mul(channels))
43            .and_then(|v| v.checked_mul(depth as usize))
44            .ok_or("image size overflow")?
45        || xindices.len()
46            != if mode == 4 {
47                target_w * channels
48            } else {
49                target_w
50            }
51        || yindices.len() != target_h
52        || xweights.len() != xindices.len()
53        || yweights.len() != target_h
54        || xindices.iter().flatten().any(|&x| x >= width)
55        || yindices.iter().flatten().any(|&y| y >= height)
56        || xindices
57            .iter()
58            .zip(xweights)
59            .any(|(i, w)| i.len() != w.len())
60        || yindices
61            .iter()
62            .zip(yweights)
63            .any(|(i, w)| i.len() != w.len())
64    {
65        return Err("invalid resize buffer or coefficient table".into());
66    }
67    let output_size = target_w
68        .checked_mul(target_h)
69        .and_then(|v| v.checked_mul(channels))
70        .and_then(|v| v.checked_mul(depth as usize))
71        .ok_or("output size overflow")?;
72    let mut output = vec![0; output_size];
73    if depth == 1 && [4, 5].contains(&mode) {
74        let bits = if mode == 4 { 8 } else { 7 };
75        let rounding = 1i32 << (bits - 1);
76        for y in 0..target_h {
77            for x in 0..target_w {
78                for c in 0..channels {
79                    let coefficient = if mode == 4 { x * channels + c } else { x };
80                    let vertical = |column: usize| -> i32 {
81                        let a = data[(yindices[y][0] * width + xindices[coefficient][column])
82                            * channels
83                            + c] as i32;
84                        let b = data[(yindices[y][1] * width + xindices[coefficient][column])
85                            * channels
86                            + c] as i32;
87                        (a * yweights[y][0] as i32 + b * yweights[y][1] as i32 + rounding) >> bits
88                    };
89                    output[(y * target_w + x) * channels + c] =
90                        ((vertical(0) * xweights[coefficient][0] as i32
91                            + vertical(1) * xweights[coefficient][1] as i32
92                            + rounding)
93                            >> bits) as u8;
94                }
95            }
96        }
97        return Ok(output);
98    }
99    let mut rows: HashMap<usize, Vec<f32>> = HashMap::new();
100    for y in 0..target_h {
101        rows.retain(|key, _| yindices[y].contains(key));
102        for &source_y in &yindices[y] {
103            rows.entry(source_y).or_insert_with(|| {
104                let mut row = vec![0f32; target_w * channels];
105                for x in 0..target_w {
106                    for channel in 0..channels {
107                        let mut sum = 0f32;
108                        for (&index, &weight) in xindices[x].iter().zip(&xweights[x]) {
109                            let pixel =
110                                read(data, (source_y * width + index) * channels + channel, depth);
111                            sum += pixel * weight;
112                        }
113                        row[x * channels + channel] = sum;
114                    }
115                }
116                row
117            });
118        }
119        // 当前行只解析一次缓存索引,避免每个像素重复进行哈希查找。
120        let vertical_rows: Vec<&[f32]> = yindices[y]
121            .iter()
122            .map(|index| rows[index].as_slice())
123            .collect();
124        for x in 0..target_w * channels {
125            if depth == 1 && mode == 1 {
126                let mut sum = 0i32;
127                for (tap, &weight) in yweights[y].iter().enumerate() {
128                    sum += (((vertical_rows[tap][x] as i64 >> 4) * weight as i64) >> 16) as i32;
129                }
130                output[(y * target_w * channels) + x] = ((sum + 2) >> 2).clamp(0, 255) as u8;
131            } else if depth == 1 && mode == 6 && x < target_w * channels / 8 * 8 {
132                let mut sum = vertical_rows[3][x] * (yweights[y][3] / (1u32 << 22) as f32);
133                for index in (0..3).rev() {
134                    sum = vertical_rows[index][x]
135                        .mul_add(yweights[y][index] / (1u32 << 22) as f32, sum);
136                }
137                write(&mut output, y * target_w * channels + x, sum, depth);
138            } else if depth == 1 && [2, 6].contains(&mode) {
139                let mut sum = 0i64;
140                for (tap, &weight) in yweights[y].iter().enumerate() {
141                    sum += vertical_rows[tap][x] as i64 * weight as i64;
142                }
143                output[y * target_w * channels + x] = ((sum + (1 << 21)) >> 22).clamp(0, 255) as u8;
144            } else {
145                let mut sum = 0f32;
146                for (tap, &weight) in yweights[y].iter().enumerate() {
147                    sum += vertical_rows[tap][x] * weight;
148                }
149                if mode == 7 {
150                    sum = (sum + 0.5).floor();
151                }
152                write(&mut output, y * target_w * channels + x, sum, depth);
153            }
154        }
155    }
156    Ok(output)
157}
158
159/// 压缩标签并查集路径,不保留图像对象。
160fn find(parents: &mut [usize], mut index: usize) -> usize {
161    while parents[index] != index {
162        parents[index] = parents[parents[index]];
163        index = parents[index];
164    }
165    index
166}
167
168/// 两遍扫描标记八连通域,按首次遇到的分区生成确定标签。
169pub fn components(
170    data: &[u8],
171    width: usize,
172    height: usize,
173) -> Result<(Vec<u8>, Vec<[i32; 5]>), String> {
174    if width.checked_mul(height) != Some(data.len())
175        || width > i32::MAX as usize
176        || height > i32::MAX as usize
177    {
178        return Err("invalid component mask".into());
179    }
180    let mut labels = vec![0usize; data.len()];
181    let mut parents = vec![0usize];
182    for y in 0..height {
183        for x in 0..width {
184            if data[y * width + x] == 0 {
185                continue;
186            }
187            // 四邻接标签使用栈数组,密集二值图不产生逐像素堆分配。
188            let mut roots = [0usize; 4];
189            let neighbors = [
190                if x > 0 { labels[y * width + x - 1] } else { 0 },
191                if y > 0 && x > 0 {
192                    labels[(y - 1) * width + x - 1]
193                } else {
194                    0
195                },
196                if y > 0 {
197                    labels[(y - 1) * width + x]
198                } else {
199                    0
200                },
201                if y > 0 && x + 1 < width {
202                    labels[(y - 1) * width + x + 1]
203                } else {
204                    0
205                },
206            ];
207            let mut minimum = usize::MAX;
208            for (i, neighbor) in neighbors.into_iter().enumerate() {
209                if neighbor != 0 {
210                    roots[i] = find(&mut parents, neighbor);
211                    minimum = minimum.min(roots[i]);
212                }
213            }
214            let label = if minimum != usize::MAX {
215                for root in roots {
216                    if root != 0 {
217                        parents[root] = minimum;
218                    }
219                }
220                minimum
221            } else {
222                let index = parents.len();
223                parents.push(index);
224                index
225            };
226            labels[y * width + x] = label;
227        }
228    }
229    let roots: Vec<usize> = (0..parents.len()).map(|v| find(&mut parents, v)).collect();
230    let mut mapping = vec![0usize; parents.len()];
231    let mut count = 1;
232    // 二乘二块按行优先发现分区,保持历史八连通组件顺序。
233    for y in (0..height).step_by(2) {
234        for x in (0..width).step_by(2) {
235            for dy in 0..2 {
236                for dx in 0..2 {
237                    if y + dy < height && x + dx < width {
238                        let root = roots[labels[(y + dy) * width + x + dx]];
239                        if root != 0 && mapping[root] == 0 {
240                            mapping[root] = count;
241                            count += 1;
242                        }
243                    }
244                }
245            }
246        }
247    }
248    let mut stats = vec![[i32::MAX, i32::MAX, -1, -1, 0]; count];
249    let mut output = Vec::with_capacity(data.len() * 4);
250    for (index, label) in labels.into_iter().enumerate() {
251        let id = mapping[roots[label]];
252        output.extend_from_slice(&(id as i32).to_ne_bytes());
253        let x = (index % width) as i32;
254        let y = (index / width) as i32;
255        let stat = &mut stats[id];
256        stat[0] = stat[0].min(x);
257        stat[1] = stat[1].min(y);
258        stat[2] = stat[2].max(x);
259        stat[3] = stat[3].max(y);
260        stat[4] += 1;
261    }
262    for stat in &mut stats {
263        if stat[4] == 0 {
264            *stat = [-1, -1, 0, 0, 0];
265        } else {
266            stat[2] -= stat[0] - 1;
267            stat[3] -= stat[1] - 1;
268        }
269    }
270    Ok((output, stats))
271}
272
273/// 八邻域边界追踪与直线简化;遍历语义对应公开参考实现。
274pub fn contours(
275    data: &[u8],
276    width: usize,
277    height: usize,
278    external: bool,
279) -> Result<Vec<Vec<[i32; 2]>>, String> {
280    if width.checked_mul(height) != Some(data.len()) {
281        return Err("invalid contour mask".into());
282    }
283    let stride = width + 2;
284    let mut pixels = vec![0i16; stride * (height + 2)];
285    for y in 0..height {
286        for x in 0..width {
287            pixels[(y + 1) * stride + x + 1] = i16::from(data[y * width + x] != 0);
288        }
289    }
290    let deltas: [isize; 8] = [
291        1,
292        1 - stride as isize,
293        -(stride as isize),
294        -1 - stride as isize,
295        -1,
296        stride as isize - 1,
297        stride as isize,
298        stride as isize + 1,
299    ];
300    let mut result = Vec::new();
301    for y in 1..=height {
302        let mut previous = 0i16;
303        let mut last_boundary = y * stride;
304        for x in 1..=width {
305            let index = y * stride + x;
306            let current = pixels[index];
307            if current == previous {
308                continue;
309            }
310            let outer = previous == 0 && current == 1;
311            let hole = current == 0 && previous >= 1;
312            if !outer && !hole {
313                previous = current;
314                if previous & -2 != 0 {
315                    last_boundary = index;
316                }
317                continue;
318            }
319            if hole && previous & -2 != 0 {
320                last_boundary = index - 1;
321            }
322            if external && (hole || pixels[last_boundary] > 0) {
323                previous = current;
324                continue;
325            }
326            let start = index - usize::from(hole);
327            let mut direction = if hole { 0 } else { 4 };
328            let end = direction;
329            let mut first;
330            loop {
331                direction = (direction + 7) & 7;
332                first = (start as isize + deltas[direction]) as usize;
333                if pixels[first] != 0 || direction == end {
334                    break;
335                }
336            }
337            let mut points = Vec::new();
338            if direction == end {
339                pixels[start] = -126;
340                points.push([(start % stride) as i32 - 1, (start / stride) as i32 - 1]);
341            } else {
342                let mut position = start;
343                let mut previous_direction = direction ^ 4;
344                let mut terminated = false;
345                for _ in 0..pixels.len() * 8 {
346                    let end = direction;
347                    let mut next_direction = direction + 1;
348                    let mut following;
349                    loop {
350                        following = (position as isize + deltas[next_direction & 7]) as usize;
351                        if pixels[following] != 0 {
352                            break;
353                        }
354                        next_direction += 1;
355                    }
356                    direction = next_direction & 7;
357                    if (direction as u32).wrapping_sub(1) < end as u32 {
358                        pixels[position] = -126;
359                    } else if pixels[position] == 1 {
360                        pixels[position] = 2;
361                    }
362                    if direction != previous_direction {
363                        points.push([
364                            (position % stride) as i32 - 1,
365                            (position / stride) as i32 - 1,
366                        ]);
367                        previous_direction = direction;
368                    }
369                    if following == start && position == first {
370                        terminated = true;
371                        break;
372                    }
373                    position = following;
374                    direction = (direction + 4) & 7;
375                }
376                if !terminated {
377                    return Err("contour traversal did not terminate".into());
378                }
379            }
380            result.push(points);
381            last_boundary = start;
382            previous = pixels[index];
383        }
384    }
385    result.reverse();
386    Ok(result)
387}
388
389/// 以 IEEE 偶数舍入量化到半精度,然后返回可直接参与运算的 float32。
390fn half_round(value: f64) -> f32 {
391    let bits = value.to_bits();
392    let exponent = ((bits >> 52) & 2047) as i32 - 1023;
393    if exponent < -14 {
394        return ((value * 16777216.).round_ties_even() / 16777216.) as f32;
395    }
396    if exponent >= 16 {
397        return if value.is_sign_negative() {
398            f32::NEG_INFINITY
399        } else {
400            f32::INFINITY
401        };
402    }
403    let mask = (1u64 << 42) - 1;
404    let remainder = bits & mask;
405    let mut rounded = bits & !mask;
406    if remainder > 1u64 << 41 || (remainder == 1u64 << 41 && (rounded >> 42) & 1 != 0) {
407        rounded += 1u64 << 42;
408    }
409    f64::from_bits(rounded) as f32
410}
411
412/// 加乘中间使用 double 精度,结果只量化一次至约定的半精度。
413fn half_fma(a: f32, b: f32, c: f32) -> f32 {
414    half_round(a as f64 * b as f64 + c as f64)
415}
416
417/// 连续三次采样使用同一权重式,八位图明确保留半精度数值契约。
418fn cubic_weights(x: f32, half: bool) -> [f32; 4] {
419    if half {
420        let x = half_round(x as f64);
421        let a2 = half_round((x * x) as f64);
422        let b = half_round((1. - x) as f64);
423        let b2 = half_round((b * b) as f64);
424        let w0 = half_round((-0.75 * half_round((x * b2) as f64)) as f64);
425        let w3 = half_round((-0.75 * half_round((a2 * b) as f64)) as f64);
426        let w1 = half_fma(a2, half_fma(1.25, x, -2.25), 1.);
427        let w2 = half_round((half_round((half_round((1. - w0) as f64) - w1) as f64) - w3) as f64);
428        [w0, w1, w2, w3]
429    } else {
430        let a2 = x * x;
431        let b = 1. - x;
432        let w0 = -0.75 * (x * (b * b));
433        let w3 = -0.75 * (a2 * b);
434        let w1 = a2.mul_add(1.25f32.mul_add(x, -2.25), 1.);
435        [w0, w1, 1. - w0 - w1 - w3, w3]
436    }
437}
438
439/// 采样边界时使用显式常数或复制模式,不调用其他视觉运行时。
440#[allow(clippy::too_many_arguments)]
441fn sample(
442    data: &[u8],
443    width: usize,
444    height: usize,
445    channels: usize,
446    depth: u8,
447    x: i64,
448    y: i64,
449    c: usize,
450    replicate: bool,
451    value: f32,
452) -> f32 {
453    if !replicate && (x < 0 || y < 0 || x >= width as i64 || y >= height as i64) {
454        return value;
455    }
456    let x = x.clamp(0, width as i64 - 1) as usize;
457    let y = y.clamp(0, height as i64 - 1) as usize;
458    read(data, (y * width + x) * channels + c, depth)
459}
460
461/// 分行逆映射避免整幅坐标临时数组,释放 GIL 后可独立并发执行。
462#[allow(clippy::too_many_arguments)]
463pub fn warp(
464    data: &[u8],
465    width: usize,
466    height: usize,
467    channels: usize,
468    depth: u8,
469    target_w: usize,
470    target_h: usize,
471    mode: u8,
472    replicate: bool,
473    value: f32,
474    inverse: &[f64; 9],
475    affine: bool,
476) -> Result<Vec<u8>, String> {
477    if width == 0
478        || height == 0
479        || data.len()
480            != width
481                .checked_mul(height)
482                .and_then(|v| v.checked_mul(channels))
483                .and_then(|v| v.checked_mul(depth as usize))
484                .ok_or("image size overflow")?
485    {
486        return Err("invalid warp image".into());
487    }
488    let length = target_w
489        .checked_mul(target_h)
490        .and_then(|v| v.checked_mul(channels))
491        .and_then(|v| v.checked_mul(depth as usize))
492        .ok_or("output size overflow")?;
493    let mut output = vec![0u8; length];
494    let half = depth == 1 && channels != 2;
495    let m: Vec<f32> = inverse.iter().map(|&v| v as f32).collect();
496    for y in 0..target_h {
497        let row: Vec<f32> = (0..3)
498            .map(|i| {
499                if depth == 4 {
500                    (y as f32).mul_add(m[i * 3 + 1], m[i * 3 + 2])
501                } else {
502                    (y as f64 * inverse[i * 3 + 1] + inverse[i * 3 + 2]) as f32
503                }
504            })
505            .collect();
506        let cubic_row: Vec<f32> = (0..3)
507            .map(|i| (y as f32).mul_add(m[i * 3 + 1], m[i * 3 + 2]))
508            .collect();
509        for x in 0..target_w {
510            let (sx, sy) = if mode == 2 {
511                if affine {
512                    (
513                        (x as f32).mul_add(m[0], cubic_row[0]),
514                        (x as f32).mul_add(m[3], cubic_row[1]),
515                    )
516                } else {
517                    let d = cubic_row[2] as f64 + x as f64 * m[6] as f64;
518                    if d == 0. {
519                        (0., 0.)
520                    } else {
521                        (
522                            ((cubic_row[0] as f64 + x as f64 * m[0] as f64) / d) as f32,
523                            ((cubic_row[1] as f64 + x as f64 * m[3] as f64) / d) as f32,
524                        )
525                    }
526                }
527            } else if x < target_w / 8 * 8 {
528                let d = (x as f32).mul_add(m[6], row[2]);
529                if d == 0. {
530                    (0., 0.)
531                } else {
532                    (
533                        (x as f32).mul_add(m[0], row[0]) / d,
534                        (x as f32).mul_add(m[3], row[1]) / d,
535                    )
536                }
537            } else {
538                let d = (x as f64 * inverse[6] + y as f64 * inverse[7] + inverse[8]) as f32;
539                if d == 0. {
540                    (0., 0.)
541                } else {
542                    (
543                        ((x as f64 * inverse[0] + y as f64 * inverse[1] + inverse[2]) / d as f64)
544                            as f32,
545                        ((x as f64 * inverse[3] + y as f64 * inverse[4] + inverse[5]) / d as f64)
546                            as f32,
547                    )
548                }
549            };
550            let bx = sx.floor() as i64;
551            let by = sy.floor() as i64;
552            let alpha = sx - bx as f32;
553            let beta = sy - by as f32;
554            let wx = cubic_weights(alpha, half);
555            let wy = cubic_weights(beta, half);
556            for c in 0..channels {
557                let pixel = |ix: i64, iy: i64| {
558                    sample(
559                        data, width, height, channels, depth, ix, iy, c, replicate, value,
560                    )
561                };
562                let result = if mode == 0 {
563                    pixel(sx.round_ties_even() as i64, sy.round_ties_even() as i64)
564                } else if mode == 1 {
565                    let p00 = pixel(bx, by);
566                    let p01 = pixel(bx + 1, by);
567                    let p10 = pixel(bx, by + 1);
568                    let p11 = pixel(bx + 1, by + 1);
569                    let top = alpha.mul_add(p01 - p00, p00);
570                    let bottom = alpha.mul_add(p11 - p10, p10);
571                    beta.mul_add(bottom - top, top)
572                } else {
573                    let mut sum = 0f32;
574                    for (j, &yweight) in wy.iter().enumerate() {
575                        let mut horizontal = pixel(bx - 1, by + j as i64 - 1) * wx[0];
576                        if half {
577                            horizontal = half_round(horizontal as f64);
578                        }
579                        for (i, &xweight) in wx.iter().enumerate().skip(1) {
580                            let p = pixel(bx + i as i64 - 1, by + j as i64 - 1);
581                            horizontal = if half {
582                                half_fma(p, xweight, horizontal)
583                            } else {
584                                p.mul_add(xweight, horizontal)
585                            };
586                        }
587                        sum = if half {
588                            half_fma(horizontal, yweight, sum)
589                        } else {
590                            horizontal.mul_add(yweight, sum)
591                        };
592                    }
593                    sum
594                };
595                write(
596                    &mut output,
597                    (y * target_w + x) * channels + c,
598                    result,
599                    depth,
600                );
601            }
602        }
603    }
604    Ok(output)
605}
606
607/// 旋转卡尺接收有序凸包,等面积时使用最后一次接触边的裁决。
608pub fn minimum_rectangle(hull: &[[f32; 2]]) -> [f32; 5] {
609    let n = hull.len();
610    if n == 0 {
611        return [0., 0., 0., 0., -90.];
612    }
613    if n == 1 {
614        return [hull[0][0], hull[0][1], 0., 0., -90.];
615    }
616    if n == 2 {
617        let dx = (hull[0][0] - hull[1][0]) as f64;
618        let dy = (hull[0][1] - hull[1][1]) as f64;
619        let mut w = 0.;
620        let mut h = dx.hypot(dy) as f32;
621        let mut angle = -90.;
622        if dx == 0. {
623            std::mem::swap(&mut w, &mut h);
624        } else if dy < 0. {
625            angle = dy.atan2(dx).to_degrees() as f32;
626            std::mem::swap(&mut w, &mut h);
627        } else if dy > 0. {
628            angle = -dx.atan2(dy).to_degrees() as f32;
629        }
630        return [
631            (hull[0][0] + hull[1][0]) * 0.5,
632            (hull[0][1] + hull[1][1]) * 0.5,
633            w,
634            h,
635            angle,
636        ];
637    }
638    let vectors: Vec<[f32; 2]> = (0..n)
639        .map(|i| {
640            [
641                hull[(i + 1) % n][0] - hull[i][0],
642                hull[(i + 1) % n][1] - hull[i][1],
643            ]
644        })
645        .collect();
646    let lengths: Vec<f32> = vectors
647        .iter()
648        .map(|v| (1. / (v[0] as f64).hypot(v[1] as f64)) as f32)
649        .collect();
650    let extremum = |axis: usize, maximum: bool| {
651        let mut idx = 0;
652        for i in 1..n {
653            if if maximum {
654                hull[i][axis] > hull[idx][axis]
655            } else {
656                hull[i][axis] < hull[idx][axis]
657            } {
658                idx = i;
659            }
660        }
661        idx
662    };
663    let mut seq = [
664        extremum(1, false),
665        extremum(0, true),
666        extremum(1, true),
667        extremum(0, false),
668    ];
669    let mut area = f32::INFINITY;
670    let (mut left, mut a, mut b, mut width, mut height, mut bottom) =
671        (0, 0f32, 0f32, 0f32, 0f32, 0);
672    for _ in 0..n {
673        let v0 = vectors[seq[0]];
674        let v1 = vectors[seq[1]];
675        let v2 = vectors[seq[2]];
676        let v3 = vectors[seq[3]];
677        let rot = [v0, [v1[1], -v1[0]], [-v2[0], -v2[1]], [-v3[1], v3[0]]];
678        let mut main = 0;
679        for i in 1..4 {
680            if rot[i][1].mul_add(rot[main][0], -rot[i][0] * rot[main][1]) < 0. {
681                main = i;
682            }
683        }
684        let lead_x = vectors[seq[main]][0] * lengths[seq[main]];
685        let lead_y = vectors[seq[main]][1] * lengths[seq[main]];
686        let (ba, bb) = [
687            (lead_x, lead_y),
688            (lead_y, -lead_x),
689            (-lead_x, -lead_y),
690            (-lead_y, lead_x),
691        ][main];
692        seq[main] = (seq[main] + 1) % n;
693        let dx = hull[seq[1]][0] - hull[seq[3]][0];
694        let dy = hull[seq[1]][1] - hull[seq[3]][1];
695        let bw = dx.mul_add(ba, dy * bb);
696        let dx = hull[seq[2]][0] - hull[seq[0]][0];
697        let dy = hull[seq[2]][1] - hull[seq[0]][1];
698        let bh = (-dx).mul_add(bb, dy * ba);
699        if bw * bh <= area {
700            area = bw * bh;
701            left = seq[3];
702            a = ba;
703            b = bb;
704            width = bw;
705            height = bh;
706            bottom = seq[0];
707        }
708    }
709    let c1 = a.mul_add(hull[left][0], hull[left][1] * b);
710    let c2 = (-b).mul_add(hull[bottom][0], hull[bottom][1] * a);
711    let inverse = 1. / a.mul_add(a, b * b);
712    let px = c1.mul_add(a, -c2 * b) * inverse;
713    let py = a.mul_add(c2, b * c1) * inverse;
714    let edge1 = [a * width, b * width];
715    let edge2 = [-b * height, a * height];
716    let mut w = (edge2[0] as f64).hypot(edge2[1] as f64) as f32;
717    let mut h = (edge1[0] as f64).hypot(edge1[1] as f64) as f32;
718    let angle = if edge1[0] == 0. && edge1[1] > 0. {
719        std::mem::swap(&mut w, &mut h);
720        -90.
721    } else {
722        -(edge1[0] as f64).atan2(edge1[1] as f64).to_degrees() as f32
723    };
724    [
725        px + (edge1[0] + edge2[0]) * 0.5,
726        py + (edge1[1] + edge2[1]) * 0.5,
727        w,
728        h,
729        angle,
730    ]
731}
732
733/// 矩形极值使用单调队列逐轴处理,复杂度不随核面积增长。
734pub fn morphology(
735    data: &[u8],
736    width: usize,
737    height: usize,
738    kw: usize,
739    kh: usize,
740    dilate: bool,
741) -> Result<Vec<u8>, String> {
742    if width.checked_mul(height) != Some(data.len()) || kw == 0 || kh == 0 {
743        return Err("invalid morphology buffer".into());
744    }
745    let border = if dilate { 0 } else { 255 };
746    let pass = |input: &[u8], vertical: bool, kernel: usize| {
747        let length = if vertical { height } else { width };
748        let lines = if vertical { width } else { height };
749        let mut output = vec![0u8; data.len()];
750        for line in 0..lines {
751            let mut queue = std::collections::VecDeque::<(usize, u8)>::new();
752            for p in 0..length + kernel - 1 {
753                let coordinate = p as isize - (kernel / 2) as isize;
754                let value = if coordinate < 0 || coordinate >= length as isize {
755                    border
756                } else {
757                    input[if vertical {
758                        coordinate as usize * width + line
759                    } else {
760                        line * width + coordinate as usize
761                    }]
762                };
763                while queue
764                    .back()
765                    .is_some_and(|v| if dilate { v.1 <= value } else { v.1 >= value })
766                {
767                    queue.pop_back();
768                }
769                queue.push_back((p, value));
770                while queue.front().is_some_and(|v| v.0 + kernel <= p) {
771                    queue.pop_front();
772                }
773                if p + 1 >= kernel {
774                    let position = p + 1 - kernel;
775                    output[if vertical {
776                        position * width + line
777                    } else {
778                        line * width + position
779                    }] = queue.front().unwrap().1;
780                }
781            }
782        }
783        output
784    };
785    Ok(pass(&pass(data, false, kw), true, kh))
786}
787
788/// 通用凸包保留首个原始索引,并按循环单调输入顺序确定起点。
789pub fn convex_hull(points: &[[f32; 2]]) -> Vec<[f32; 2]> {
790    let mut values: Vec<([f32; 2], usize)> = points
791        .iter()
792        .copied()
793        .enumerate()
794        .map(|(i, p)| (p, i))
795        .collect();
796    values.sort_unstable_by(|a, b| {
797        a.0[0]
798            .total_cmp(&b.0[0])
799            .then_with(|| a.0[1].total_cmp(&b.0[1]))
800            .then(a.1.cmp(&b.1))
801    });
802    values.dedup_by(|a, b| a.0 == b.0);
803    if values.len() < 2 {
804        return values.into_iter().map(|v| v.0).collect();
805    }
806    let cross = |a: ([f32; 2], usize), b: ([f32; 2], usize), c: ([f32; 2], usize)| {
807        (b.0[0] - a.0[0]) as f64 * (c.0[1] - a.0[1]) as f64
808            - (b.0[1] - a.0[1]) as f64 * (c.0[0] - a.0[0]) as f64
809    };
810    let mut lower = Vec::new();
811    let mut upper = Vec::new();
812    for &point in &values {
813        while lower.len() >= 2 && cross(lower[lower.len() - 2], lower[lower.len() - 1], point) <= 0.
814        {
815            lower.pop();
816        }
817        lower.push(point);
818    }
819    for &point in values.iter().rev() {
820        while upper.len() >= 2 && cross(upper[upper.len() - 2], upper[upper.len() - 1], point) <= 0.
821        {
822            upper.pop();
823        }
824        upper.push(point);
825    }
826    lower.pop();
827    upper.pop();
828    lower.extend(upper);
829    let n = lower.len();
830    let mut start = 0;
831    for i in 1..n {
832        if lower[i].0[0] > lower[start].0[0]
833            || (lower[i].0[0] == lower[start].0[0] && lower[i].0[1] > lower[start].0[1])
834        {
835            start = i;
836        }
837    }
838    lower.rotate_left(start);
839    if n >= 3 {
840        for increasing in [true, false] {
841            let start = if increasing {
842                lower.iter().enumerate().min_by_key(|(_, v)| v.1).unwrap().0
843            } else {
844                lower.iter().enumerate().max_by_key(|(_, v)| v.1).unwrap().0
845            };
846            if (0..n - 1)
847                .all(|i| (lower[(start + i) % n].1 < lower[(start + i + 1) % n].1) == increasing)
848            {
849                lower.rotate_left(start);
850                break;
851            }
852        }
853    }
854    lower.into_iter().map(|v| v.0).collect()
855}
856
857/// 小型矩阵使用显式主元和融合加乘消元,避免 BLAS 选择不同舍入顺序。
858pub fn linear_solve(matrix: &[Vec<f64>], rhs: &[f64]) -> Option<Vec<f64>> {
859    let n = matrix.len();
860    if n == 0 || n > 16 || rhs.len() != n || matrix.iter().any(|row| row.len() != n) {
861        return None;
862    }
863    let mut a = matrix.to_vec();
864    let mut b = rhs.to_vec();
865    for i in 0..n {
866        let mut pivot = i;
867        for j in i + 1..n {
868            if a[j][i].abs() > a[pivot][i].abs() {
869                pivot = j;
870            }
871        }
872        if a[pivot][i].abs() < f64::EPSILON * 100. {
873            return None;
874        }
875        if pivot != i {
876            // 已消元列不再读取,交换整行不改变有效列的计算顺序。
877            a.swap(i, pivot);
878            b.swap(i, pivot);
879        }
880        let reciprocal = -1. / a[i][i];
881        let (prior, following) = a.split_at_mut(i + 1);
882        let pivot_row = &prior[i];
883        for (offset, row) in following.iter_mut().enumerate() {
884            let alpha = row[i] * reciprocal;
885            for (value, &coefficient) in row[i + 1..].iter_mut().zip(&pivot_row[i + 1..]) {
886                *value = alpha.mul_add(coefficient, *value);
887            }
888            let j = i + 1 + offset;
889            b[j] = alpha.mul_add(b[i], b[j]);
890        }
891    }
892    for i in (0..n).rev() {
893        let mut sum = b[i];
894        for (k, &value) in b.iter().enumerate().skip(i + 1) {
895            sum = (-a[i][k]).mul_add(value, sum);
896        }
897        b[i] = sum / a[i][i];
898    }
899    Some(b)
900}