1#![forbid(unsafe_code)]
7#![allow(clippy::cast_lossless)]
8#![allow(clippy::cast_precision_loss)]
9#![allow(clippy::cast_possible_truncation)]
10#![allow(clippy::cast_sign_loss)]
11#![allow(clippy::cast_possible_wrap)]
12#![allow(clippy::similar_names)]
13#![allow(clippy::many_single_char_names)]
14#![allow(clippy::missing_errors_doc)]
15#![allow(clippy::match_same_arms)]
16#![allow(clippy::doc_markdown)]
17#![allow(clippy::unused_self)]
18#![allow(clippy::unnecessary_cast)]
19#![allow(clippy::bool_to_int_with_if)]
20#![allow(clippy::needless_range_loop)]
21#![allow(clippy::too_many_lines)]
22#![allow(clippy::unnecessary_wraps)]
23#![allow(clippy::map_unwrap_or)]
24#![allow(clippy::no_effect_underscore_binding)]
25#![allow(clippy::unreadable_literal)]
26#![allow(dead_code)]
27
28use std::collections::VecDeque;
29
30use crate::error::{GraphError, GraphResult};
31use crate::frame::FilterFrame;
32use crate::node::{Node, NodeId, NodeState, NodeType};
33use crate::port::{InputPort, OutputPort, PortFormat, PortId, PortType, VideoPortFormat};
34use oximedia_codec::{Plane, VideoFrame};
35use oximedia_core::Timestamp;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
39pub enum DeinterlaceMode {
40 Bob,
42 Weave,
44 #[default]
46 Blend,
47 Yadif,
49 YadifSpatial,
51}
52
53impl DeinterlaceMode {
54 #[must_use]
56 pub fn doubles_framerate(&self) -> bool {
57 matches!(self, Self::Bob | Self::Yadif)
58 }
59
60 #[must_use]
62 pub fn requires_temporal(&self) -> bool {
63 matches!(self, Self::Yadif)
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
69pub enum FieldOrder {
70 #[default]
72 TopFieldFirst,
73 BottomFieldFirst,
75 Auto,
77}
78
79impl FieldOrder {
80 #[must_use]
82 pub fn first_field_start(&self) -> usize {
83 match self {
84 Self::TopFieldFirst | Self::Auto => 0,
85 Self::BottomFieldFirst => 1,
86 }
87 }
88
89 #[must_use]
91 pub fn second_field_start(&self) -> usize {
92 match self {
93 Self::TopFieldFirst | Self::Auto => 1,
94 Self::BottomFieldFirst => 0,
95 }
96 }
97}
98
99#[derive(Clone, Debug)]
101pub struct DeinterlaceConfig {
102 pub mode: DeinterlaceMode,
104 pub field_order: FieldOrder,
106 pub auto_detect: bool,
108 pub detection_threshold: f64,
110}
111
112impl Default for DeinterlaceConfig {
113 fn default() -> Self {
114 Self {
115 mode: DeinterlaceMode::default(),
116 field_order: FieldOrder::default(),
117 auto_detect: false,
118 detection_threshold: 0.5,
119 }
120 }
121}
122
123impl DeinterlaceConfig {
124 #[must_use]
126 pub fn new(mode: DeinterlaceMode) -> Self {
127 Self {
128 mode,
129 ..Default::default()
130 }
131 }
132
133 #[must_use]
135 pub fn with_field_order(mut self, order: FieldOrder) -> Self {
136 self.field_order = order;
137 self
138 }
139
140 #[must_use]
142 pub fn with_auto_detect(mut self, enabled: bool) -> Self {
143 self.auto_detect = enabled;
144 self
145 }
146
147 #[must_use]
149 pub fn with_detection_threshold(mut self, threshold: f64) -> Self {
150 self.detection_threshold = threshold.clamp(0.0, 1.0);
151 self
152 }
153}
154
155pub struct DeinterlaceFilter {
171 id: NodeId,
172 name: String,
173 state: NodeState,
174 inputs: Vec<InputPort>,
175 outputs: Vec<OutputPort>,
176 config: DeinterlaceConfig,
177 frame_buffer: VecDeque<VideoFrame>,
179 output_frame_idx: u64,
181 pending_output: Vec<VideoFrame>,
183}
184
185impl DeinterlaceFilter {
186 #[must_use]
188 pub fn new(id: NodeId, name: impl Into<String>, config: DeinterlaceConfig) -> Self {
189 Self {
190 id,
191 name: name.into(),
192 state: NodeState::Idle,
193 inputs: vec![InputPort::new(PortId(0), "input", PortType::Video)
194 .with_format(PortFormat::Video(VideoPortFormat::any()))],
195 outputs: vec![OutputPort::new(PortId(0), "output", PortType::Video)
196 .with_format(PortFormat::Video(VideoPortFormat::any()))],
197 config,
198 frame_buffer: VecDeque::with_capacity(3),
199 output_frame_idx: 0,
200 pending_output: Vec::new(),
201 }
202 }
203
204 #[must_use]
206 pub fn config(&self) -> &DeinterlaceConfig {
207 &self.config
208 }
209
210 fn detect_interlaced(&self, frame: &VideoFrame) -> bool {
212 if frame.planes.is_empty() {
213 return false;
214 }
215
216 let plane = &frame.planes[0];
217 let height = frame.height as usize;
218 let width = frame.width as usize;
219
220 let mut comb_score = 0u64;
222 let mut total_samples = 0u64;
223
224 for y in 1..height - 1 {
225 let row_prev = plane.row(y - 1);
226 let row_curr = plane.row(y);
227 let row_next = plane.row(y + 1);
228
229 for x in 0..width {
230 let prev = row_prev.get(x).copied().unwrap_or(0) as i32;
231 let curr = row_curr.get(x).copied().unwrap_or(0) as i32;
232 let next = row_next.get(x).copied().unwrap_or(0) as i32;
233
234 let interpolated = (prev + next) / 2;
236 let diff = (curr - interpolated).unsigned_abs() as u64;
237
238 if diff > 20 {
239 comb_score += diff;
240 }
241 total_samples += 1;
242 }
243 }
244
245 if total_samples == 0 {
246 return false;
247 }
248
249 let score = comb_score as f64 / total_samples as f64;
250 score > self.config.detection_threshold * 10.0
251 }
252
253 fn bob_deinterlace(&self, frame: &VideoFrame, field: usize) -> VideoFrame {
255 let mut output = VideoFrame::new(frame.format, frame.width, frame.height);
256 output.frame_type = frame.frame_type;
257 output.color_info = frame.color_info;
258
259 let half_duration =
261 frame.timestamp.timebase.den as i64 / (frame.timestamp.timebase.num as i64 * 2);
262 let pts_offset = if field == 0 { 0 } else { half_duration };
263 output.timestamp =
264 Timestamp::new(frame.timestamp.pts + pts_offset, frame.timestamp.timebase);
265
266 for (plane_idx, src_plane) in frame.planes.iter().enumerate() {
267 let (width, height) = frame.plane_dimensions(plane_idx);
268 let mut dst_data = vec![0u8; (width * height) as usize];
269
270 let field_start = if field == 0 {
271 self.config.field_order.first_field_start()
272 } else {
273 self.config.field_order.second_field_start()
274 };
275
276 for y in 0..height as usize {
277 let is_field_line = (y + field_start) % 2 == 0;
278
279 if is_field_line {
280 let src_row = src_plane.row(y);
282 for x in 0..width as usize {
283 dst_data[y * width as usize + x] = src_row.get(x).copied().unwrap_or(0);
284 }
285 } else {
286 let y_prev = y.saturating_sub(1);
288 let y_next = (y + 1).min(height as usize - 1);
289
290 let prev_row = src_plane.row(y_prev);
291 let next_row = src_plane.row(y_next);
292
293 for x in 0..width as usize {
294 let prev = prev_row.get(x).copied().unwrap_or(0) as u16;
295 let next = next_row.get(x).copied().unwrap_or(0) as u16;
296 dst_data[y * width as usize + x] = ((prev + next) / 2) as u8;
297 }
298 }
299 }
300
301 output.planes.push(Plane::new(dst_data, width as usize));
302 }
303
304 output
305 }
306
307 fn weave_deinterlace(&self, frame: &VideoFrame) -> VideoFrame {
309 frame.clone()
312 }
313
314 fn blend_deinterlace(&self, frame: &VideoFrame) -> VideoFrame {
316 let mut output = VideoFrame::new(frame.format, frame.width, frame.height);
317 output.timestamp = frame.timestamp;
318 output.frame_type = frame.frame_type;
319 output.color_info = frame.color_info;
320
321 for (plane_idx, src_plane) in frame.planes.iter().enumerate() {
322 let (width, height) = frame.plane_dimensions(plane_idx);
323 let mut dst_data = vec![0u8; (width * height) as usize];
324
325 for y in 0..height as usize {
326 let curr_row = src_plane.row(y);
327
328 if y == 0 || y == height as usize - 1 {
329 for x in 0..width as usize {
331 dst_data[y * width as usize + x] = curr_row.get(x).copied().unwrap_or(0);
332 }
333 } else {
334 let prev_row = src_plane.row(y - 1);
336 let next_row = src_plane.row(y + 1);
337
338 for x in 0..width as usize {
339 let prev = prev_row.get(x).copied().unwrap_or(0) as u32;
340 let curr = curr_row.get(x).copied().unwrap_or(0) as u32;
341 let next = next_row.get(x).copied().unwrap_or(0) as u32;
342
343 let blended = (prev + curr * 2 + next) / 4;
345 dst_data[y * width as usize + x] = blended as u8;
346 }
347 }
348 }
349
350 output.planes.push(Plane::new(dst_data, width as usize));
351 }
352
353 output
354 }
355
356 fn yadif_deinterlace(&self, field: usize) -> Option<VideoFrame> {
358 if self.frame_buffer.len() < 2 {
360 return None;
361 }
362
363 let curr_idx = if self.frame_buffer.len() >= 3 { 1 } else { 0 };
364 let frame = &self.frame_buffer[curr_idx];
365
366 let prev_frame = self.frame_buffer.front();
367 let next_frame = if self.frame_buffer.len() >= 3 {
368 self.frame_buffer.get(2)
369 } else {
370 self.frame_buffer.get(1)
371 };
372
373 let mut output = VideoFrame::new(frame.format, frame.width, frame.height);
374 output.frame_type = frame.frame_type;
375 output.color_info = frame.color_info;
376
377 let half_duration =
379 frame.timestamp.timebase.den as i64 / (frame.timestamp.timebase.num as i64 * 2);
380 let pts_offset = if field == 0 { 0 } else { half_duration };
381 output.timestamp =
382 Timestamp::new(frame.timestamp.pts + pts_offset, frame.timestamp.timebase);
383
384 for (plane_idx, src_plane) in frame.planes.iter().enumerate() {
385 let (width, height) = frame.plane_dimensions(plane_idx);
386 let mut dst_data = vec![0u8; (width * height) as usize];
387
388 let field_start = if field == 0 {
389 self.config.field_order.first_field_start()
390 } else {
391 self.config.field_order.second_field_start()
392 };
393
394 let prev_plane = prev_frame.and_then(|f| f.planes.get(plane_idx));
395 let next_plane = next_frame.and_then(|f| f.planes.get(plane_idx));
396
397 for y in 0..height as usize {
398 let is_field_line = (y + field_start) % 2 == 0;
399
400 if is_field_line {
401 let src_row = src_plane.row(y);
403 for x in 0..width as usize {
404 dst_data[y * width as usize + x] = src_row.get(x).copied().unwrap_or(0);
405 }
406 } else {
407 for x in 0..width as usize {
409 let pixel = self.yadif_pixel(
410 src_plane,
411 prev_plane,
412 next_plane,
413 x,
414 y,
415 width as usize,
416 height as usize,
417 );
418 dst_data[y * width as usize + x] = pixel;
419 }
420 }
421 }
422
423 output.planes.push(Plane::new(dst_data, width as usize));
424 }
425
426 Some(output)
427 }
428
429 #[allow(clippy::too_many_arguments)]
431 fn yadif_pixel(
432 &self,
433 curr: &Plane,
434 prev: Option<&Plane>,
435 next: Option<&Plane>,
436 x: usize,
437 y: usize,
438 width: usize,
439 height: usize,
440 ) -> u8 {
441 let y_prev = y.saturating_sub(1);
442 let y_next = (y + 1).min(height - 1);
443
444 let c_prev = curr.row(y_prev).get(x).copied().unwrap_or(0) as i32;
446 let c_next = curr.row(y_next).get(x).copied().unwrap_or(0) as i32;
447 let spatial = (c_prev + c_next) / 2;
448
449 let temporal = if let (Some(p), Some(n)) = (prev, next) {
451 let p_curr = p.row(y).get(x).copied().unwrap_or(0) as i32;
452 let n_curr = n.row(y).get(x).copied().unwrap_or(0) as i32;
453 (p_curr + n_curr) / 2
454 } else {
455 spatial
456 };
457
458 let edge_prev = curr
460 .row(y_prev)
461 .get(x.saturating_sub(1))
462 .copied()
463 .unwrap_or(0) as i32;
464 let edge_next = curr
465 .row(y_next)
466 .get((x + 1).min(width - 1))
467 .copied()
468 .unwrap_or(0) as i32;
469
470 let spatial_diff = (c_prev - c_next).abs();
471 let edge_diff = (edge_prev - edge_next).abs();
472
473 let result = if spatial_diff > edge_diff * 2 {
475 temporal
476 } else {
477 spatial
478 };
479
480 result.clamp(0, 255) as u8
481 }
482
483 fn process_frame(&mut self, frame: VideoFrame) -> Vec<VideoFrame> {
485 if self.config.auto_detect && !self.detect_interlaced(&frame) {
487 return vec![frame];
488 }
489
490 self.frame_buffer.push_back(frame);
491
492 while self.frame_buffer.len() > 3 {
494 self.frame_buffer.pop_front();
495 }
496
497 let mut output = Vec::new();
498
499 match self.config.mode {
500 DeinterlaceMode::Bob => {
501 if let Some(frame) = self.frame_buffer.back() {
502 output.push(self.bob_deinterlace(frame, 0));
503 output.push(self.bob_deinterlace(frame, 1));
504 }
505 }
506 DeinterlaceMode::Weave => {
507 if let Some(frame) = self.frame_buffer.back() {
508 output.push(self.weave_deinterlace(frame));
509 }
510 }
511 DeinterlaceMode::Blend => {
512 if let Some(frame) = self.frame_buffer.back() {
513 output.push(self.blend_deinterlace(frame));
514 }
515 }
516 DeinterlaceMode::Yadif => {
517 if let Some(frame) = self.yadif_deinterlace(0) {
518 output.push(frame);
519 }
520 if let Some(frame) = self.yadif_deinterlace(1) {
521 output.push(frame);
522 }
523 }
524 DeinterlaceMode::YadifSpatial => {
525 if let Some(frame) = self.frame_buffer.back() {
527 output.push(self.blend_deinterlace(frame));
528 }
529 }
530 }
531
532 self.output_frame_idx += output.len() as u64;
533 output
534 }
535}
536
537impl Node for DeinterlaceFilter {
538 fn id(&self) -> NodeId {
539 self.id
540 }
541
542 fn name(&self) -> &str {
543 &self.name
544 }
545
546 fn node_type(&self) -> NodeType {
547 NodeType::Filter
548 }
549
550 fn state(&self) -> NodeState {
551 self.state
552 }
553
554 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
555 if !self.state.can_transition_to(state) {
556 return Err(GraphError::InvalidStateTransition {
557 node: self.id,
558 from: self.state.to_string(),
559 to: state.to_string(),
560 });
561 }
562 self.state = state;
563 Ok(())
564 }
565
566 fn inputs(&self) -> &[InputPort] {
567 &self.inputs
568 }
569
570 fn outputs(&self) -> &[OutputPort] {
571 &self.outputs
572 }
573
574 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
575 if !self.pending_output.is_empty() {
577 return Ok(Some(FilterFrame::Video(self.pending_output.remove(0))));
578 }
579
580 match input {
581 Some(FilterFrame::Video(frame)) => {
582 let mut output_frames = self.process_frame(frame);
583
584 if output_frames.is_empty() {
585 Ok(None)
586 } else {
587 let first = output_frames.remove(0);
588 self.pending_output = output_frames;
589 Ok(Some(FilterFrame::Video(first)))
590 }
591 }
592 Some(_) => Err(GraphError::PortTypeMismatch {
593 expected: "Video".to_string(),
594 actual: "Audio".to_string(),
595 }),
596 None => Ok(None),
597 }
598 }
599
600 fn flush(&mut self) -> GraphResult<Vec<FilterFrame>> {
601 let mut output: Vec<FilterFrame> = self
602 .pending_output
603 .drain(..)
604 .map(FilterFrame::Video)
605 .collect();
606
607 while let Some(frame) = self.frame_buffer.pop_front() {
609 output.push(FilterFrame::Video(frame));
610 }
611
612 Ok(output)
613 }
614
615 fn reset(&mut self) -> GraphResult<()> {
616 self.frame_buffer.clear();
617 self.pending_output.clear();
618 self.output_frame_idx = 0;
619 self.set_state(NodeState::Idle)
620 }
621}
622
623#[derive(Debug)]
625pub struct InterlaceDetector {
626 threshold: f64,
628 frames_analyzed: u64,
630 interlaced_count: u64,
632 detected_field_order: Option<FieldOrder>,
634}
635
636impl Default for InterlaceDetector {
637 fn default() -> Self {
638 Self {
639 threshold: 0.5,
640 frames_analyzed: 0,
641 interlaced_count: 0,
642 detected_field_order: None,
643 }
644 }
645}
646
647impl InterlaceDetector {
648 #[must_use]
650 pub fn new(threshold: f64) -> Self {
651 Self {
652 threshold: threshold.clamp(0.0, 1.0),
653 ..Default::default()
654 }
655 }
656
657 pub fn analyze(&mut self, frame: &VideoFrame) -> bool {
659 self.frames_analyzed += 1;
660
661 if frame.planes.is_empty() {
662 return false;
663 }
664
665 let is_interlaced = self.detect_combing(frame);
666 if is_interlaced {
667 self.interlaced_count += 1;
668 }
669
670 is_interlaced
671 }
672
673 fn detect_combing(&self, frame: &VideoFrame) -> bool {
675 let plane = &frame.planes[0];
676 let height = frame.height as usize;
677 let width = frame.width as usize;
678
679 let mut comb_score = 0u64;
680 let mut samples = 0u64;
681
682 for y in (2..height - 2).step_by(4) {
684 for x in (0..width).step_by(4) {
685 let row_m2 = plane.row(y - 2);
686 let row_m1 = plane.row(y - 1);
687 let row_0 = plane.row(y);
688 let row_p1 = plane.row(y + 1);
689 let row_p2 = plane.row(y + 2);
690
691 let m2 = row_m2.get(x).copied().unwrap_or(0) as i32;
692 let m1 = row_m1.get(x).copied().unwrap_or(0) as i32;
693 let c = row_0.get(x).copied().unwrap_or(0) as i32;
694 let p1 = row_p1.get(x).copied().unwrap_or(0) as i32;
695 let p2 = row_p2.get(x).copied().unwrap_or(0) as i32;
696
697 let diff = ((m1 - c).abs() + (p1 - c).abs()) - ((m2 - c).abs() + (p2 - c).abs());
699 if diff > 20 {
700 comb_score += diff.unsigned_abs() as u64;
701 }
702 samples += 1;
703 }
704 }
705
706 if samples == 0 {
707 return false;
708 }
709
710 let score = comb_score as f64 / samples as f64;
711 score > self.threshold * 10.0
712 }
713
714 #[must_use]
716 pub fn interlaced_percentage(&self) -> f64 {
717 if self.frames_analyzed == 0 {
718 0.0
719 } else {
720 self.interlaced_count as f64 / self.frames_analyzed as f64
721 }
722 }
723
724 #[must_use]
726 pub fn is_interlaced(&self) -> bool {
727 self.interlaced_percentage() > 0.5
728 }
729
730 #[must_use]
732 pub fn field_order(&self) -> Option<FieldOrder> {
733 self.detected_field_order
734 }
735}
736
737#[cfg(test)]
738mod tests {
739 use super::*;
740 use oximedia_core::PixelFormat;
741
742 fn create_test_frame(width: u32, height: u32) -> VideoFrame {
743 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
744 frame.allocate();
745
746 if let Some(plane) = frame.planes.get_mut(0) {
748 let mut data = vec![0u8; (width * height) as usize];
749 for y in 0..height as usize {
750 for x in 0..width as usize {
751 data[y * width as usize + x] = if y % 2 == 0 { 200 } else { 50 };
753 }
754 }
755 *plane = Plane::new(data, width as usize);
756 }
757
758 frame
759 }
760
761 fn create_progressive_frame(width: u32, height: u32) -> VideoFrame {
762 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
763 frame.allocate();
764
765 if let Some(plane) = frame.planes.get_mut(0) {
767 let mut data = vec![0u8; (width * height) as usize];
768 for y in 0..height as usize {
769 for x in 0..width as usize {
770 data[y * width as usize + x] = ((y * 255) / height as usize) as u8;
771 }
772 }
773 *plane = Plane::new(data, width as usize);
774 }
775
776 frame
777 }
778
779 #[test]
780 fn test_deinterlace_mode_properties() {
781 assert!(DeinterlaceMode::Bob.doubles_framerate());
782 assert!(DeinterlaceMode::Yadif.doubles_framerate());
783 assert!(!DeinterlaceMode::Blend.doubles_framerate());
784 assert!(!DeinterlaceMode::Weave.doubles_framerate());
785
786 assert!(DeinterlaceMode::Yadif.requires_temporal());
787 assert!(!DeinterlaceMode::Bob.requires_temporal());
788 }
789
790 #[test]
791 fn test_field_order() {
792 assert_eq!(FieldOrder::TopFieldFirst.first_field_start(), 0);
793 assert_eq!(FieldOrder::TopFieldFirst.second_field_start(), 1);
794 assert_eq!(FieldOrder::BottomFieldFirst.first_field_start(), 1);
795 assert_eq!(FieldOrder::BottomFieldFirst.second_field_start(), 0);
796 }
797
798 #[test]
799 fn test_deinterlace_config() {
800 let config = DeinterlaceConfig::new(DeinterlaceMode::Bob)
801 .with_field_order(FieldOrder::BottomFieldFirst)
802 .with_auto_detect(true)
803 .with_detection_threshold(0.7);
804
805 assert_eq!(config.mode, DeinterlaceMode::Bob);
806 assert_eq!(config.field_order, FieldOrder::BottomFieldFirst);
807 assert!(config.auto_detect);
808 assert!((config.detection_threshold - 0.7).abs() < 0.001);
809 }
810
811 #[test]
812 fn test_deinterlace_filter_creation() {
813 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend);
814 let filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
815
816 assert_eq!(filter.id(), NodeId(0));
817 assert_eq!(filter.name(), "deinterlace");
818 assert_eq!(filter.node_type(), NodeType::Filter);
819 }
820
821 #[test]
822 fn test_bob_deinterlace() {
823 let config = DeinterlaceConfig::new(DeinterlaceMode::Bob);
824 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
825
826 let input = create_test_frame(64, 48);
827 let output_frames = filter.process_frame(input);
828
829 assert_eq!(output_frames.len(), 2);
831 }
832
833 #[test]
834 fn test_blend_deinterlace() {
835 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend);
836 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
837
838 let input = create_test_frame(64, 48);
839 let output_frames = filter.process_frame(input);
840
841 assert_eq!(output_frames.len(), 1);
843 }
844
845 #[test]
846 fn test_weave_deinterlace() {
847 let config = DeinterlaceConfig::new(DeinterlaceMode::Weave);
848 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
849
850 let input = create_test_frame(64, 48);
851 let output_frames = filter.process_frame(input);
852
853 assert_eq!(output_frames.len(), 1);
854 }
855
856 #[test]
857 fn test_auto_detect_progressive() {
858 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend).with_auto_detect(true);
859 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
860
861 let input = create_progressive_frame(64, 48);
862 let output_frames = filter.process_frame(input);
863
864 assert_eq!(output_frames.len(), 1);
866 }
867
868 #[test]
869 fn test_interlace_detector() {
870 let mut detector = InterlaceDetector::new(0.5);
871
872 let interlaced = create_test_frame(64, 48);
873 let _is_interlaced = detector.analyze(&interlaced);
874
875 assert!(detector.frames_analyzed > 0);
876 }
878
879 #[test]
880 fn test_interlace_detector_percentage() {
881 let detector = InterlaceDetector {
882 threshold: 0.5,
883 frames_analyzed: 10,
884 interlaced_count: 7,
885 detected_field_order: None,
886 };
887
888 assert!((detector.interlaced_percentage() - 0.7).abs() < 0.001);
889 assert!(detector.is_interlaced());
890 }
891
892 #[test]
893 fn test_node_process() {
894 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend);
895 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
896
897 let input = create_test_frame(64, 48);
898 let result = filter
899 .process(Some(FilterFrame::Video(input)))
900 .expect("operation should succeed")
901 .expect("operation should succeed");
902
903 assert!(matches!(result, FilterFrame::Video(_)));
904 }
905
906 #[test]
907 fn test_node_state_transitions() {
908 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend);
909 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
910
911 assert_eq!(filter.state(), NodeState::Idle);
912 filter
913 .set_state(NodeState::Processing)
914 .expect("set_state should succeed");
915 assert_eq!(filter.state(), NodeState::Processing);
916 }
917
918 #[test]
919 fn test_process_none_input() {
920 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend);
921 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
922
923 let result = filter.process(None).expect("process should succeed");
924 assert!(result.is_none());
925 }
926
927 #[test]
928 fn test_reset() {
929 let config = DeinterlaceConfig::new(DeinterlaceMode::Blend);
930 let mut filter = DeinterlaceFilter::new(NodeId(0), "deinterlace", config);
931
932 let input = create_test_frame(64, 48);
933 let _ = filter.process(Some(FilterFrame::Video(input)));
934
935 filter.reset().expect("reset should succeed");
936
937 assert!(filter.frame_buffer.is_empty());
938 assert_eq!(filter.output_frame_idx, 0);
939 }
940}