Skip to main content

oximedia_gpu/kernels/
resize.rs

1//! Image resize kernels with various interpolation methods
2
3use crate::{GpuDevice, Result};
4
5/// Resize interpolation filter
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ResizeFilter {
8    /// Nearest neighbor - fastest, lowest quality
9    Nearest,
10    /// Bilinear interpolation - balanced quality/speed
11    Bilinear,
12    /// Bicubic interpolation - high quality, slower
13    Bicubic,
14    /// Lanczos resampling - highest quality, slowest
15    Lanczos,
16    /// Area averaging - good for downscaling
17    Area,
18}
19
20impl ResizeFilter {
21    /// Get the filter ID for shader dispatch
22    #[must_use]
23    pub fn to_id(self) -> u32 {
24        match self {
25            Self::Nearest => 0,
26            Self::Bilinear => 1,
27            Self::Bicubic => 2,
28            Self::Lanczos => 3,
29            Self::Area => 4,
30        }
31    }
32
33    /// Get the filter name
34    #[must_use]
35    pub fn name(self) -> &'static str {
36        match self {
37            Self::Nearest => "Nearest",
38            Self::Bilinear => "Bilinear",
39            Self::Bicubic => "Bicubic",
40            Self::Lanczos => "Lanczos",
41            Self::Area => "Area",
42        }
43    }
44
45    /// Get the filter kernel radius
46    #[must_use]
47    pub fn kernel_radius(self) -> u32 {
48        match self {
49            Self::Nearest => 0,
50            Self::Bilinear => 1,
51            Self::Bicubic => 2,
52            Self::Lanczos => 3,
53            Self::Area => 1,
54        }
55    }
56}
57
58/// Image resize kernel
59pub struct ResizeKernel {
60    filter: ResizeFilter,
61}
62
63impl ResizeKernel {
64    /// Create a new resize kernel
65    #[must_use]
66    pub fn new(filter: ResizeFilter) -> Self {
67        Self { filter }
68    }
69
70    /// Execute the resize operation
71    ///
72    /// # Arguments
73    ///
74    /// * `device` - GPU device
75    /// * `input` - Input image buffer
76    /// * `src_width` - Source image width
77    /// * `src_height` - Source image height
78    /// * `output` - Output image buffer
79    /// * `dst_width` - Destination image width
80    /// * `dst_height` - Destination image height
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if the resize operation fails.
85    #[allow(clippy::too_many_arguments)]
86    pub fn execute(
87        &self,
88        device: &GpuDevice,
89        input: &[u8],
90        _src_width: u32,
91        _src_height: u32,
92        output: &mut [u8],
93        dst_width: u32,
94        dst_height: u32,
95    ) -> Result<()> {
96        // Delegate to the ops module implementation
97        crate::ops::ScaleOperation::scale(
98            device,
99            input,
100            _src_width,
101            _src_height,
102            output,
103            dst_width,
104            dst_height,
105            self.filter.into(),
106        )
107    }
108
109    /// Get the filter type
110    #[must_use]
111    pub fn filter(&self) -> ResizeFilter {
112        self.filter
113    }
114
115    /// Calculate output buffer size
116    #[must_use]
117    pub fn output_size(dst_width: u32, dst_height: u32, channels: u32) -> usize {
118        (dst_width * dst_height * channels) as usize
119    }
120
121    /// Estimate FLOPS for the resize operation
122    #[must_use]
123    pub fn estimate_flops(
124        _src_width: u32,
125        _src_height: u32,
126        dst_width: u32,
127        dst_height: u32,
128        filter: ResizeFilter,
129    ) -> u64 {
130        let output_pixels = u64::from(dst_width) * u64::from(dst_height);
131        let kernel_size = u64::from(filter.kernel_radius());
132        let samples_per_pixel = (kernel_size * 2 + 1) * (kernel_size * 2 + 1);
133
134        // Each sample requires interpolation (4-8 ops per sample)
135        output_pixels * samples_per_pixel * 6
136    }
137}
138
139impl From<ResizeFilter> for crate::ops::ScaleFilter {
140    fn from(filter: ResizeFilter) -> Self {
141        match filter {
142            ResizeFilter::Nearest => Self::Nearest,
143            ResizeFilter::Bilinear => Self::Bilinear,
144            ResizeFilter::Bicubic => Self::Bicubic,
145            ResizeFilter::Area => Self::Area,
146            ResizeFilter::Lanczos => Self::Bicubic, // Fallback to bicubic for Lanczos
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_resize_filter_properties() {
157        assert_eq!(ResizeFilter::Nearest.to_id(), 0);
158        assert_eq!(ResizeFilter::Bilinear.to_id(), 1);
159        assert_eq!(ResizeFilter::Bicubic.to_id(), 2);
160
161        assert_eq!(ResizeFilter::Nearest.kernel_radius(), 0);
162        assert_eq!(ResizeFilter::Bilinear.kernel_radius(), 1);
163        assert_eq!(ResizeFilter::Bicubic.kernel_radius(), 2);
164        assert_eq!(ResizeFilter::Lanczos.kernel_radius(), 3);
165    }
166
167    #[test]
168    fn test_output_size_calculation() {
169        assert_eq!(ResizeKernel::output_size(1920, 1080, 4), 1920 * 1080 * 4);
170        assert_eq!(ResizeKernel::output_size(640, 480, 3), 640 * 480 * 3);
171    }
172
173    #[test]
174    fn test_flops_estimation() {
175        let flops = ResizeKernel::estimate_flops(1920, 1080, 960, 540, ResizeFilter::Bilinear);
176        assert!(flops > 0);
177
178        let flops_bicubic =
179            ResizeKernel::estimate_flops(1920, 1080, 960, 540, ResizeFilter::Bicubic);
180        assert!(flops_bicubic > flops); // Bicubic should be more expensive
181    }
182}