1#![forbid(unsafe_code)]
12#![allow(dead_code)]
13#![allow(clippy::too_many_arguments)]
14#![allow(clippy::cast_possible_truncation)]
15#![allow(clippy::cast_sign_loss)]
16#![allow(clippy::cast_possible_wrap)]
17#![allow(clippy::missing_panics_doc)]
18#![allow(clippy::must_use_candidate)]
19#![allow(clippy::let_and_return)]
20#![allow(clippy::manual_let_else)]
21
22use super::diamond::AdaptiveDiamond;
23use super::search::{MotionSearch, SearchConfig, SearchContext};
24use super::types::{BlockMatch, BlockSize, MotionVector, SearchRange};
25
26pub const MAX_PYRAMID_LEVELS: usize = 4;
28
29pub const MIN_PYRAMID_DIMENSION: usize = 16;
31
32#[derive(Clone, Debug)]
34pub struct HierarchicalConfig {
35 pub levels: usize,
37 pub coarse_range: SearchRange,
39 pub refine_range: SearchRange,
41 pub adaptive_levels: bool,
43}
44
45impl Default for HierarchicalConfig {
46 fn default() -> Self {
47 Self {
48 levels: 3,
49 coarse_range: SearchRange::symmetric(16),
50 refine_range: SearchRange::symmetric(4),
51 adaptive_levels: true,
52 }
53 }
54}
55
56impl HierarchicalConfig {
57 #[must_use]
59 pub const fn new(levels: usize) -> Self {
60 Self {
61 levels,
62 coarse_range: SearchRange::symmetric(16),
63 refine_range: SearchRange::symmetric(4),
64 adaptive_levels: true,
65 }
66 }
67
68 #[must_use]
70 pub const fn levels(mut self, levels: usize) -> Self {
71 self.levels = levels;
72 self
73 }
74
75 #[must_use]
77 pub const fn coarse_range(mut self, range: SearchRange) -> Self {
78 self.coarse_range = range;
79 self
80 }
81
82 #[must_use]
84 pub const fn refine_range(mut self, range: SearchRange) -> Self {
85 self.refine_range = range;
86 self
87 }
88}
89
90#[derive(Clone, Debug)]
92pub struct PyramidLevel {
93 pub data: Vec<u8>,
95 pub width: usize,
97 pub height: usize,
99 pub stride: usize,
101 pub scale: usize,
103}
104
105impl PyramidLevel {
106 #[must_use]
108 pub fn new(width: usize, height: usize, scale: usize) -> Self {
109 let stride = width;
110 Self {
111 data: vec![0u8; stride * height],
112 width,
113 height,
114 stride,
115 scale,
116 }
117 }
118
119 #[must_use]
121 pub fn from_data(data: Vec<u8>, width: usize, height: usize, scale: usize) -> Self {
122 let stride = width;
123 Self {
124 data,
125 width,
126 height,
127 stride,
128 scale,
129 }
130 }
131
132 #[must_use]
134 pub fn get_pixel(&self, x: usize, y: usize) -> u8 {
135 if x < self.width && y < self.height {
136 self.data[y * self.stride + x]
137 } else {
138 0
139 }
140 }
141
142 pub fn set_pixel(&mut self, x: usize, y: usize, value: u8) {
144 if x < self.width && y < self.height {
145 self.data[y * self.stride + x] = value;
146 }
147 }
148
149 pub fn downsample_from(&mut self, src: &PyramidLevel) {
151 for y in 0..self.height {
152 for x in 0..self.width {
153 let src_x = x * 2;
154 let src_y = y * 2;
155
156 let p00 = u32::from(src.get_pixel(src_x, src_y));
158 let p01 = u32::from(src.get_pixel(src_x + 1, src_y));
159 let p10 = u32::from(src.get_pixel(src_x, src_y + 1));
160 let p11 = u32::from(src.get_pixel(src_x + 1, src_y + 1));
161
162 let avg = ((p00 + p01 + p10 + p11 + 2) / 4) as u8;
163 self.set_pixel(x, y, avg);
164 }
165 }
166 }
167
168 #[must_use]
170 pub fn block_data(&self, x: usize, y: usize) -> &[u8] {
171 let offset = y * self.stride + x;
172 &self.data[offset..]
173 }
174}
175
176#[derive(Clone, Debug)]
178pub struct ImagePyramid {
179 levels: Vec<PyramidLevel>,
181}
182
183impl ImagePyramid {
184 #[must_use]
186 pub const fn new() -> Self {
187 Self { levels: Vec::new() }
188 }
189
190 pub fn build(&mut self, src: &[u8], width: usize, height: usize, num_levels: usize) {
192 self.levels.clear();
193
194 let level0 = PyramidLevel::from_data(src.to_vec(), width, height, 1);
196 self.levels.push(level0);
197
198 let mut cur_width = width;
200 let mut cur_height = height;
201 let mut cur_scale = 1;
202
203 for _ in 1..num_levels {
204 cur_width /= 2;
205 cur_height /= 2;
206 cur_scale *= 2;
207
208 if cur_width < MIN_PYRAMID_DIMENSION || cur_height < MIN_PYRAMID_DIMENSION {
209 break;
210 }
211
212 if let Some(prev) = self.levels.last() {
213 let mut level = PyramidLevel::new(cur_width, cur_height, cur_scale);
214 level.downsample_from(prev);
215 self.levels.push(level);
216 }
217 }
218 }
219
220 #[must_use]
222 pub fn num_levels(&self) -> usize {
223 self.levels.len()
224 }
225
226 #[must_use]
228 pub fn get_level(&self, index: usize) -> Option<&PyramidLevel> {
229 self.levels.get(index)
230 }
231
232 #[must_use]
234 pub fn coarsest(&self) -> Option<&PyramidLevel> {
235 self.levels.last()
236 }
237
238 #[must_use]
240 pub fn finest(&self) -> Option<&PyramidLevel> {
241 self.levels.first()
242 }
243}
244
245impl Default for ImagePyramid {
246 fn default() -> Self {
247 Self::new()
248 }
249}
250
251#[derive(Clone, Debug)]
253pub struct HierarchicalSearch {
254 src_pyramid: ImagePyramid,
256 ref_pyramid: ImagePyramid,
258 config: HierarchicalConfig,
260 searcher: AdaptiveDiamond,
262}
263
264impl Default for HierarchicalSearch {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270impl HierarchicalSearch {
271 #[must_use]
273 pub fn new() -> Self {
274 Self {
275 src_pyramid: ImagePyramid::new(),
276 ref_pyramid: ImagePyramid::new(),
277 config: HierarchicalConfig::default(),
278 searcher: AdaptiveDiamond::new(),
279 }
280 }
281
282 #[must_use]
284 pub fn with_config(mut self, config: HierarchicalConfig) -> Self {
285 self.config = config;
286 self
287 }
288
289 pub fn build_pyramids(
291 &mut self,
292 src: &[u8],
293 src_width: usize,
294 src_height: usize,
295 reference: &[u8],
296 ref_width: usize,
297 ref_height: usize,
298 ) {
299 let levels = self.config.levels.min(MAX_PYRAMID_LEVELS);
300 self.src_pyramid.build(src, src_width, src_height, levels);
301 self.ref_pyramid
302 .build(reference, ref_width, ref_height, levels);
303 }
304
305 pub fn search_hierarchical(
310 &self,
311 block_x: usize,
312 block_y: usize,
313 block_size: BlockSize,
314 search_config: &SearchConfig,
315 ) -> BlockMatch {
316 let num_levels = self
317 .src_pyramid
318 .num_levels()
319 .min(self.ref_pyramid.num_levels());
320
321 if num_levels == 0 {
322 return BlockMatch::worst();
323 }
324
325 let mut current_mv = MotionVector::zero();
327
328 for level_idx in (0..num_levels).rev() {
330 let src_level = match self.src_pyramid.get_level(level_idx) {
331 Some(l) => l,
332 None => continue,
333 };
334 let ref_level = match self.ref_pyramid.get_level(level_idx) {
335 Some(l) => l,
336 None => continue,
337 };
338
339 let scale = src_level.scale;
341 let scaled_x = block_x / scale;
342 let scaled_y = block_y / scale;
343 let scaled_width = block_size.width() / scale;
344 let scaled_height = block_size.height() / scale;
345
346 if scaled_width < 4 || scaled_height < 4 {
348 continue;
349 }
350
351 let level_range = if level_idx == num_levels - 1 {
353 self.config.coarse_range
354 } else {
355 self.config.refine_range
356 };
357
358 let level_config = SearchConfig {
360 range: level_range,
361 ..search_config.clone()
362 };
363
364 let scaled_mv = MotionVector::from_full_pel(
366 current_mv.full_pel_x() / scale as i32,
367 current_mv.full_pel_y() / scale as i32,
368 );
369
370 let src_offset = scaled_y * src_level.stride + scaled_x;
372 if src_offset >= src_level.data.len() {
373 continue;
374 }
375
376 let ctx = SearchContext::new(
377 &src_level.data[src_offset..],
378 src_level.stride,
379 &ref_level.data,
380 ref_level.stride,
381 BlockSize::Block8x8, scaled_x,
383 scaled_y,
384 ref_level.width,
385 ref_level.height,
386 );
387
388 let result = self
390 .searcher
391 .search_with_predictor(&ctx, &level_config, scaled_mv);
392
393 current_mv = MotionVector::from_full_pel(
395 result.mv.full_pel_x() * scale as i32,
396 result.mv.full_pel_y() * scale as i32,
397 );
398 }
399
400 if let (Some(src_level), Some(ref_level)) =
402 (self.src_pyramid.finest(), self.ref_pyramid.finest())
403 {
404 let src_offset = block_y * src_level.stride + block_x;
405 if src_offset < src_level.data.len() {
406 let ctx = SearchContext::new(
407 &src_level.data[src_offset..],
408 src_level.stride,
409 &ref_level.data,
410 ref_level.stride,
411 block_size,
412 block_x,
413 block_y,
414 ref_level.width,
415 ref_level.height,
416 );
417
418 let final_config = SearchConfig {
419 range: self.config.refine_range,
420 ..search_config.clone()
421 };
422
423 return self
424 .searcher
425 .search_with_predictor(&ctx, &final_config, current_mv);
426 }
427 }
428
429 let cost = search_config.mv_cost.rd_cost(¤t_mv, u32::MAX);
430 BlockMatch::new(current_mv, u32::MAX, cost)
431 }
432
433 #[must_use]
435 pub fn calculate_levels(width: usize, height: usize, max_levels: usize) -> usize {
436 let min_dim = width.min(height);
437 let mut levels = 1;
438
439 let mut size = min_dim;
440 while size >= MIN_PYRAMID_DIMENSION * 2 && levels < max_levels {
441 size /= 2;
442 levels += 1;
443 }
444
445 levels
446 }
447}
448
449#[derive(Clone, Debug, Default)]
451pub struct CoarseToFineRefiner {
452 steps: Vec<RefinementStep>,
454}
455
456#[derive(Clone, Debug)]
458pub struct RefinementStep {
459 pub scale: usize,
461 pub range: SearchRange,
463 pub iterations: u32,
465}
466
467impl Default for RefinementStep {
468 fn default() -> Self {
469 Self {
470 scale: 1,
471 range: SearchRange::symmetric(2),
472 iterations: 4,
473 }
474 }
475}
476
477impl CoarseToFineRefiner {
478 #[must_use]
480 pub fn new() -> Self {
481 Self { steps: Vec::new() }
482 }
483
484 #[must_use]
486 pub fn add_step(mut self, scale: usize, range: i32, iterations: u32) -> Self {
487 self.steps.push(RefinementStep {
488 scale,
489 range: SearchRange::symmetric(range),
490 iterations,
491 });
492 self
493 }
494
495 #[must_use]
497 pub fn default_steps() -> Self {
498 Self::new()
499 .add_step(4, 8, 8) .add_step(2, 4, 6) .add_step(1, 2, 4) }
503
504 #[must_use]
506 pub fn steps(&self) -> &[RefinementStep] {
507 &self.steps
508 }
509
510 #[must_use]
512 pub const fn scale_mv(mv: MotionVector, from_scale: usize, to_scale: usize) -> MotionVector {
513 if from_scale == to_scale {
514 return mv;
515 }
516
517 if from_scale > to_scale {
518 let factor = (from_scale / to_scale) as i32;
520 MotionVector::new(mv.dx * factor, mv.dy * factor)
521 } else {
522 let factor = (to_scale / from_scale) as i32;
524 MotionVector::new(mv.dx / factor, mv.dy / factor)
525 }
526 }
527}
528
529pub struct ResolutionScaler;
531
532impl ResolutionScaler {
533 pub fn downsample_2x(src: &[u8], width: usize, height: usize) -> Vec<u8> {
535 let new_width = width / 2;
536 let new_height = height / 2;
537 let mut dst = vec![0u8; new_width * new_height];
538
539 for y in 0..new_height {
540 for x in 0..new_width {
541 let src_x = x * 2;
542 let src_y = y * 2;
543
544 let p00 = u32::from(src[src_y * width + src_x]);
545 let p01 = u32::from(src[src_y * width + src_x + 1]);
546 let p10 = u32::from(src[(src_y + 1) * width + src_x]);
547 let p11 = u32::from(src[(src_y + 1) * width + src_x + 1]);
548
549 dst[y * new_width + x] = ((p00 + p01 + p10 + p11 + 2) / 4) as u8;
550 }
551 }
552
553 dst
554 }
555
556 pub fn downsample_4x(src: &[u8], width: usize, height: usize) -> Vec<u8> {
558 let half = Self::downsample_2x(src, width, height);
559 Self::downsample_2x(&half, width / 2, height / 2)
560 }
561
562 #[must_use]
564 pub const fn upsample_mv(mv: MotionVector, factor: i32) -> MotionVector {
565 MotionVector::new(mv.dx * factor, mv.dy * factor)
566 }
567
568 #[must_use]
570 pub const fn downsample_mv(mv: MotionVector, factor: i32) -> MotionVector {
571 MotionVector::new(mv.dx / factor, mv.dy / factor)
572 }
573
574 #[must_use]
576 pub const fn scale_position(x: usize, y: usize, scale: usize) -> (usize, usize) {
577 (x / scale, y / scale)
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584
585 #[test]
586 fn test_pyramid_level_creation() {
587 let level = PyramidLevel::new(64, 64, 1);
588 assert_eq!(level.width, 64);
589 assert_eq!(level.height, 64);
590 assert_eq!(level.scale, 1);
591 assert_eq!(level.data.len(), 64 * 64);
592 }
593
594 #[test]
595 fn test_pyramid_level_pixel_access() {
596 let mut level = PyramidLevel::new(8, 8, 1);
597 level.set_pixel(3, 4, 128);
598 assert_eq!(level.get_pixel(3, 4), 128);
599 assert_eq!(level.get_pixel(0, 0), 0);
600 }
601
602 #[test]
603 fn test_pyramid_level_downsample() {
604 let mut src_level = PyramidLevel::new(8, 8, 1);
605 for y in 0..8 {
607 for x in 0..8 {
608 src_level.set_pixel(x, y, ((x + y) * 16) as u8);
609 }
610 }
611
612 let mut dst_level = PyramidLevel::new(4, 4, 2);
613 dst_level.downsample_from(&src_level);
614
615 assert!(dst_level.get_pixel(0, 0) > 0);
617 assert!(dst_level.get_pixel(1, 1) > dst_level.get_pixel(0, 0));
618 }
619
620 #[test]
621 fn test_image_pyramid_build() {
622 let src = vec![128u8; 64 * 64];
623 let mut pyramid = ImagePyramid::new();
624 pyramid.build(&src, 64, 64, 3);
625
626 assert_eq!(pyramid.num_levels(), 3);
627
628 assert_eq!(pyramid.get_level(0).map(|l| l.width), Some(64));
630 assert_eq!(pyramid.get_level(1).map(|l| l.width), Some(32));
631 assert_eq!(pyramid.get_level(2).map(|l| l.width), Some(16));
632 }
633
634 #[test]
635 fn test_image_pyramid_min_size() {
636 let src = vec![128u8; 32 * 32];
637 let mut pyramid = ImagePyramid::new();
638 pyramid.build(&src, 32, 32, 5);
639
640 assert!(pyramid.num_levels() <= 2);
642 }
643
644 #[test]
645 fn test_hierarchical_config() {
646 let config = HierarchicalConfig::new(4)
647 .coarse_range(SearchRange::symmetric(32))
648 .refine_range(SearchRange::symmetric(8));
649
650 assert_eq!(config.levels, 4);
651 assert_eq!(config.coarse_range.horizontal, 32);
652 assert_eq!(config.refine_range.horizontal, 8);
653 }
654
655 #[test]
656 fn test_hierarchical_search_creation() {
657 let search = HierarchicalSearch::new().with_config(HierarchicalConfig::new(3));
658
659 assert_eq!(search.config.levels, 3);
660 }
661
662 #[test]
663 fn test_calculate_pyramid_levels() {
664 assert_eq!(HierarchicalSearch::calculate_levels(128, 128, 4), 4);
665 assert_eq!(HierarchicalSearch::calculate_levels(64, 64, 4), 3);
666 assert_eq!(HierarchicalSearch::calculate_levels(32, 32, 4), 2);
667 }
668
669 #[test]
670 fn test_coarse_to_fine_refiner() {
671 let refiner = CoarseToFineRefiner::default_steps();
672 assert_eq!(refiner.steps().len(), 3);
673 }
674
675 #[test]
676 fn test_scale_mv() {
677 let mv = MotionVector::new(16, 32);
678
679 let scaled_up = CoarseToFineRefiner::scale_mv(mv, 2, 1);
681 assert_eq!(scaled_up.dx, 32);
682 assert_eq!(scaled_up.dy, 64);
683
684 let scaled_down = CoarseToFineRefiner::scale_mv(mv, 1, 2);
686 assert_eq!(scaled_down.dx, 8);
687 assert_eq!(scaled_down.dy, 16);
688 }
689
690 #[test]
691 fn test_resolution_scaler_downsample() {
692 let src = vec![
694 100, 100, 200, 200, 100, 100, 200, 200, 50, 50, 150, 150, 50, 50, 150, 150,
695 ];
696
697 let dst = ResolutionScaler::downsample_2x(&src, 4, 4);
698 assert_eq!(dst.len(), 4);
699
700 assert_eq!(dst[0], 100); assert_eq!(dst[1], 200); assert_eq!(dst[2], 50); assert_eq!(dst[3], 150); }
706
707 #[test]
708 fn test_resolution_scaler_mv() {
709 let mv = MotionVector::new(8, 16);
710
711 let up = ResolutionScaler::upsample_mv(mv, 2);
712 assert_eq!(up.dx, 16);
713 assert_eq!(up.dy, 32);
714
715 let down = ResolutionScaler::downsample_mv(mv, 2);
716 assert_eq!(down.dx, 4);
717 assert_eq!(down.dy, 8);
718 }
719
720 #[test]
721 fn test_hierarchical_search_integration() {
722 let src = vec![100u8; 64 * 64];
723 let reference = vec![100u8; 64 * 64];
724
725 let mut search = HierarchicalSearch::new().with_config(HierarchicalConfig::new(3));
726
727 search.build_pyramids(&src, 64, 64, &reference, 64, 64);
728
729 let config = SearchConfig::default();
730 let result = search.search_hierarchical(0, 0, BlockSize::Block8x8, &config);
731
732 assert_eq!(result.mv.full_pel_x(), 0);
734 assert_eq!(result.mv.full_pel_y(), 0);
735 }
736
737 #[test]
738 fn test_refinement_step() {
739 let step = RefinementStep::default();
740 assert_eq!(step.scale, 1);
741 assert_eq!(step.iterations, 4);
742 }
743}