async_cuda_npp/
resize.rs

1use async_cuda_core::runtime::Future;
2use async_cuda_core::DeviceBuffer2D;
3
4use crate::ffi;
5use crate::region::Region;
6use crate::stream::Stream;
7
8type Result<T> = std::result::Result<T, crate::error::Error>;
9
10/// Resize an image using bilinear interpolation. This function expects a reference to a device
11/// image for input, and a mutable reference to a device image to place the output in.
12///
13/// This function assumes the following about the input and output images:
14/// * Images are in RGB format.
15/// * Images are in standard memory order, i.e. HWC.
16///
17/// # Stream ordered semantics
18///
19/// This function uses stream ordered semantics. It can only be guaranteed to complete sequentially
20/// relative to operations scheduled on the same stream or the default stream.
21///
22/// # Arguments
23///
24/// * `input` - The on-device input image.
25/// * `input_region` - Specify region of interest in input image. This can be used to combine crop
26///   and resize in a single operation.
27/// * `output_region` - Specify region of interest in input image.
28/// * `output` - The on-device output image.
29/// * `stream` - Stream to use.
30pub async fn resize(
31    input: &DeviceBuffer2D<u8>,
32    input_region: Region,
33    output: &mut DeviceBuffer2D<u8>,
34    output_region: Region,
35    stream: &Stream,
36) -> Result<()> {
37    assert_eq!(input.num_channels(), 3, "input image must be in RGB format");
38    assert_eq!(
39        output.num_channels(),
40        3,
41        "output image must be in RGB format"
42    );
43
44    let context = stream.to_context();
45    Future::new(move || {
46        ffi::resize::resize(
47            input.inner(),
48            input_region,
49            output.inner_mut(),
50            output_region,
51            &context,
52        )
53    })
54    .await
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    use crate::stream::Stream;
62    use crate::tests::image::*;
63    use crate::tests::memory::*;
64
65    use async_cuda_core::DeviceBuffer2D;
66
67    #[tokio::test]
68    async fn test_resize() {
69        // This is the expected result when resizing the RGB flag to 2 by 2 with bilinear
70        // interpolation.
71        const OUTPUT: Image2x2 = [[R, R], [R, B]];
72        const OUTPUT_FLAT: [u8; 2 * 2 * 3] = flatten!(OUTPUT, 2 * 2 * 3);
73
74        let stream = Stream::new().await.unwrap();
75
76        let image = to_device_2d!(&RGB_FLAG, 4, 4, 3, &stream);
77        let mut output = DeviceBuffer2D::<u8>::new(2, 2, 3).await;
78        resize(&image, Region::Full, &mut output, Region::Full, &stream)
79            .await
80            .unwrap();
81
82        let output = to_host_2d!(output, &stream);
83        assert_eq!(&output, &OUTPUT_FLAT);
84    }
85
86    #[tokio::test]
87    async fn test_resize_with_input_region() {
88        // This is the raw expected result when resizing the center part of the RGB flag from two by
89        // to two four by four.
90        #[rustfmt::skip]
91        #[allow(clippy::zero_prefixed_literal)]
92        const OUTPUT: [u8; 4 * 4 * 3] = [
93            000, 255, 000, 000, 255, 000, 000, 255, 000, 064, 191, 000,
94            000, 191, 064, 000, 191, 064, 000, 191, 064, 064, 143, 048,
95            000, 064, 191, 000, 064, 191, 000, 064, 191, 064, 048, 143,
96            064, 000, 191, 064, 000, 191, 064, 000, 191, 112, 000, 143,
97        ];
98
99        let stream = Stream::new().await.unwrap();
100
101        let image = to_device_2d!(&RGB_FLAG, 4, 4, 3, &stream);
102        let center = Region::Rectangle {
103            x: 1,
104            y: 1,
105            width: 2,
106            height: 2,
107        };
108        let mut output = DeviceBuffer2D::<u8>::new(4, 4, 3).await;
109        resize(&image, center, &mut output, Region::Full, &stream)
110            .await
111            .unwrap();
112
113        let output = to_host_2d!(output, &stream);
114        assert_eq!(&output, &OUTPUT);
115    }
116
117    #[tokio::test]
118    async fn test_resize_with_output_region() {
119        #[rustfmt::skip]
120        const INPUT: [u8; 2 * 2 * 3] = [
121            0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
122            0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
123        ];
124        #[rustfmt::skip]
125        const EXPECTED_OUTPUT: [u8; 2 * 2 * 3] = [
126            0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
127            0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
128        ];
129
130        let stream = Stream::new().await.unwrap();
131        let bottom_half = Region::Rectangle {
132            x: 0,
133            y: 1,
134            width: 2,
135            height: 1,
136        };
137
138        let image = to_device_2d!(&INPUT, 2, 2, 3, &stream);
139        let mut output = DeviceBuffer2D::<u8>::new(2, 2, 3).await;
140        output.fill_with_byte(0x00, &stream).await.unwrap();
141        resize(&image, Region::Full, &mut output, bottom_half, &stream)
142            .await
143            .unwrap();
144
145        let output = to_host_2d!(output, &stream);
146        assert_eq!(&output, &EXPECTED_OUTPUT);
147    }
148
149    #[tokio::test]
150    #[should_panic]
151    async fn test_it_panics_when_input_num_channels_incorrect() {
152        let input = DeviceBuffer2D::<u8>::new(100, 100, 2).await;
153        let mut output = DeviceBuffer2D::<u8>::new(200, 200, 3).await;
154        resize(
155            &input,
156            Region::Full,
157            &mut output,
158            Region::Full,
159            &Stream::null().await,
160        )
161        .await
162        .unwrap();
163    }
164
165    #[tokio::test]
166    #[should_panic]
167    async fn test_it_panics_when_output_num_channels_incorrect() {
168        let input = DeviceBuffer2D::<u8>::new(100, 100, 3).await;
169        let mut output = DeviceBuffer2D::<u8>::new(200, 200, 2).await;
170        resize(
171            &input,
172            Region::Full,
173            &mut output,
174            Region::Full,
175            &Stream::null().await,
176        )
177        .await
178        .unwrap();
179    }
180}