Skip to main content

singe_npp/image/
view.rs

1use std::{marker::PhantomData, ptr::NonNull};
2
3use singe_cuda::memory::DeviceMemory;
4use singe_npp_sys as sys;
5
6use crate::{
7    error::{Error, Result},
8    types::Size,
9    utility::{checked_len, checked_step, ensure_positive, to_usize},
10};
11
12pub trait ChannelLayout: Copy + 'static {
13    const CHANNELS: usize;
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct C1;
18
19impl ChannelLayout for C1 {
20    const CHANNELS: usize = 1;
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct C2;
25
26impl ChannelLayout for C2 {
27    const CHANNELS: usize = 2;
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub struct C3;
32
33impl ChannelLayout for C3 {
34    const CHANNELS: usize = 3;
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct C4;
39
40impl ChannelLayout for C4 {
41    const CHANNELS: usize = 4;
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub struct AC4;
46
47impl ChannelLayout for AC4 {
48    const CHANNELS: usize = 4;
49}
50
51pub type Gray = C1;
52pub type Channels2 = C2;
53pub type Rgb = C3;
54pub type Rgba = C4;
55pub type AlphaIgnoredRgba = AC4;
56
57pub type MaskView<'a> = ImageView<'a, u8, C1>;
58pub type MaskViewMut<'a> = ImageViewMut<'a, u8, C1>;
59
60#[derive(Debug, Clone, Copy)]
61pub struct ImageView<'a, T, L = C1> {
62    ptr: NonNull<T>,
63    size: Size,
64    step: i32,
65    _t: PhantomData<(&'a T, L)>,
66}
67
68impl<'a, T, L> ImageView<'a, T, L>
69where
70    L: ChannelLayout,
71{
72    pub fn from_memory(memory: &'a DeviceMemory<T>, size: Size) -> Result<Self> {
73        let len = checked_len(size, L::CHANNELS)?;
74        if len > memory.len() {
75            return Err(Error::LengthMismatch {
76                name: "image memory".into(),
77                expected: len,
78                actual: memory.len(),
79            });
80        }
81
82        Self::from_memory_with_step(
83            memory,
84            size,
85            checked_step(size.width, L::CHANNELS, size_of::<T>())?,
86        )
87    }
88
89    pub fn from_memory_with_step(
90        memory: &'a DeviceMemory<T>,
91        size: Size,
92        step: i32,
93    ) -> Result<Self> {
94        validate_image_shape::<T, L>(size, step)?;
95        validate_image_memory::<T, L>(memory.len(), size, step)?;
96
97        Ok(Self {
98            ptr: NonNull::new(memory.as_ptr().cast_mut()).ok_or(Error::NullHandle)?,
99            size,
100            step,
101            _t: PhantomData,
102        })
103    }
104
105    /// # Safety
106    ///
107    /// `ptr` must be non-null CUDA device memory aligned for `T` that remains valid for `'a`.
108    /// The allocation must cover `height - 1` full byte steps plus the final
109    /// ROI row of `width * L::CHANNELS * size_of::<T>()` bytes. `step` is a
110    /// byte pitch, not an element count, and must describe the actual row
111    /// spacing visible to NPP.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error if the image shape is invalid or `ptr` is null.
116    pub unsafe fn from_raw_parts(ptr: *const T, size: Size, step: i32) -> Result<Self> {
117        validate_image_shape::<T, L>(size, step)?;
118
119        Ok(Self {
120            ptr: NonNull::new(ptr.cast_mut()).ok_or(Error::NullHandle)?,
121            size,
122            step,
123            _t: PhantomData,
124        })
125    }
126
127    pub const fn size(&self) -> Size {
128        self.size
129    }
130
131    pub const fn step(&self) -> i32 {
132        self.step
133    }
134
135    pub fn byte_len(&self) -> Result<usize> {
136        image_byte_len::<T, L>(self.size, self.step)
137    }
138
139    pub(crate) const fn as_ptr(&self) -> *const T {
140        self.ptr.as_ptr()
141    }
142
143    pub(crate) fn descriptor(&self) -> sys::NppiImageDescriptor {
144        sys::NppiImageDescriptor {
145            pData: self.as_ptr().cast_mut().cast(),
146            nStep: self.step,
147            oSize: self.size.into(),
148        }
149    }
150}
151
152#[derive(Debug)]
153pub struct ImageViewMut<'a, T, L = C1> {
154    ptr: NonNull<T>,
155    size: Size,
156    step: i32,
157    _marker: PhantomData<(&'a mut T, L)>,
158}
159
160impl<'a, T, L> ImageViewMut<'a, T, L>
161where
162    L: ChannelLayout,
163{
164    pub fn from_memory(memory: &'a mut DeviceMemory<T>, size: Size) -> Result<Self> {
165        let len = checked_len(size, L::CHANNELS)?;
166        if len > memory.len() {
167            return Err(Error::LengthMismatch {
168                name: "image memory".into(),
169                expected: len,
170                actual: memory.len(),
171            });
172        }
173
174        Self::from_memory_with_step(
175            memory,
176            size,
177            checked_step(size.width, L::CHANNELS, size_of::<T>())?,
178        )
179    }
180
181    pub fn from_memory_with_step(
182        memory: &'a mut DeviceMemory<T>,
183        size: Size,
184        step: i32,
185    ) -> Result<Self> {
186        validate_image_shape::<T, L>(size, step)?;
187        validate_image_memory::<T, L>(memory.len(), size, step)?;
188
189        Ok(Self {
190            ptr: NonNull::new(memory.as_mut_ptr()).ok_or(Error::NullHandle)?,
191            size,
192            step,
193            _marker: PhantomData,
194        })
195    }
196
197    /// # Safety
198    ///
199    /// `ptr` must be non-null CUDA device memory aligned for `T` that remains valid for `'a`.
200    /// The allocation must cover `height - 1` full byte steps plus the final
201    /// ROI row of `width * L::CHANNELS * size_of::<T>()` bytes. `step` is a
202    /// byte pitch, not an element count, and must describe the actual row
203    /// spacing visible to NPP. The pointed-to ROI must be uniquely writable for
204    /// the returned view lifetime.
205    ///
206    /// # Errors
207    ///
208    /// Returns an error if the image shape is invalid or `ptr` is null.
209    pub unsafe fn from_raw_parts(ptr: *mut T, size: Size, step: i32) -> Result<Self> {
210        validate_image_shape::<T, L>(size, step)?;
211
212        Ok(Self {
213            ptr: NonNull::new(ptr).ok_or(Error::NullHandle)?,
214            size,
215            step,
216            _marker: PhantomData,
217        })
218    }
219
220    pub const fn size(&self) -> Size {
221        self.size
222    }
223
224    pub const fn step(&self) -> i32 {
225        self.step
226    }
227
228    pub fn byte_len(&self) -> Result<usize> {
229        image_byte_len::<T, L>(self.size, self.step)
230    }
231
232    pub(crate) const fn as_mut_ptr(&mut self) -> *mut T {
233        self.ptr.as_ptr()
234    }
235
236    pub(crate) fn descriptor(&mut self) -> sys::NppiImageDescriptor {
237        sys::NppiImageDescriptor {
238            pData: self.as_mut_ptr().cast(),
239            nStep: self.step,
240            oSize: self.size.into(),
241        }
242    }
243}
244
245#[derive(Debug, Clone, Copy)]
246pub struct PlanarImageView<'a, T, const C: usize> {
247    planes: [ImageView<'a, T, C1>; C],
248}
249
250impl<'a, T, const C: usize> PlanarImageView<'a, T, C> {
251    pub fn create(planes: [ImageView<'a, T, C1>; C]) -> Result<Self> {
252        validate_plane_count(C)?;
253        validate_same_planes(&planes)?;
254        Ok(Self { planes })
255    }
256
257    pub const fn planes(&self) -> &[ImageView<'a, T, C1>; C] {
258        &self.planes
259    }
260}
261
262#[derive(Debug)]
263pub struct PlanarImageViewMut<'a, T, const C: usize> {
264    planes: [ImageViewMut<'a, T, C1>; C],
265}
266
267impl<'a, T, const C: usize> PlanarImageViewMut<'a, T, C> {
268    pub fn create(planes: [ImageViewMut<'a, T, C1>; C]) -> Result<Self> {
269        validate_plane_count(C)?;
270        validate_same_planes(&planes)?;
271        Ok(Self { planes })
272    }
273
274    pub const fn planes(&self) -> &[ImageViewMut<'a, T, C1>; C] {
275        &self.planes
276    }
277
278    pub fn planes_mut(&mut self) -> &mut [ImageViewMut<'a, T, C1>; C] {
279        &mut self.planes
280    }
281}
282
283fn validate_image_shape<T, L>(size: Size, step: i32) -> Result<()>
284where
285    L: ChannelLayout,
286{
287    size.validate()?;
288
289    let element_size = size_of::<T>();
290    if element_size == 0 {
291        return Err(Error::OutOfRange {
292            name: "element size".into(),
293        });
294    }
295
296    let minimum_step = checked_step(size.width, L::CHANNELS, element_size)?;
297    if step < minimum_step {
298        return Err(Error::OutOfRange {
299            name: "step".into(),
300        });
301    }
302
303    Ok(())
304}
305
306fn validate_image_memory<T, L>(memory_len: usize, size: Size, step: i32) -> Result<()>
307where
308    L: ChannelLayout,
309{
310    let element_size = size_of::<T>();
311    let required_bytes = image_byte_len::<T, L>(size, step)?;
312    let available_bytes = memory_len
313        .checked_mul(element_size)
314        .ok_or_else(|| Error::OutOfRange { name: "len".into() })?;
315
316    if required_bytes > available_bytes {
317        return Err(Error::LengthMismatch {
318            name: "image memory".into(),
319            expected: required_bytes,
320            actual: available_bytes,
321        });
322    }
323
324    Ok(())
325}
326
327fn image_byte_len<T, L>(size: Size, step: i32) -> Result<usize>
328where
329    L: ChannelLayout,
330{
331    size.validate()?;
332    ensure_positive(step, "step")?;
333
334    let row_bytes = (size.width as usize)
335        .checked_mul(L::CHANNELS)
336        .and_then(|value| value.checked_mul(size_of::<T>()))
337        .ok_or_else(|| Error::OutOfRange {
338            name: "byte_len".into(),
339        })?;
340    (size.height as usize - 1)
341        .checked_mul(step as usize)
342        .and_then(|value| value.checked_add(row_bytes))
343        .ok_or_else(|| Error::OutOfRange {
344            name: "byte_len".into(),
345        })
346}
347
348fn validate_plane_count(count: usize) -> Result<()> {
349    match count {
350        2..=4 => Ok(()),
351        _ => Err(Error::OutOfRange {
352            name: "plane count".into(),
353        }),
354    }
355}
356
357fn validate_same_planes<T, P, const C: usize>(planes: &[P; C]) -> Result<()>
358where
359    P: PlaneInfo<T>,
360{
361    let Some(first) = planes.first() else {
362        return Ok(());
363    };
364
365    for plane in planes.iter().skip(1) {
366        if plane.size() != first.size() {
367            let expected = (first.size().width as usize)
368                .checked_mul(first.size().height as usize)
369                .ok_or_else(|| Error::OutOfRange { name: "len".into() })?;
370            let actual = (plane.size().width as usize)
371                .checked_mul(plane.size().height as usize)
372                .ok_or_else(|| Error::OutOfRange { name: "len".into() })?;
373            return Err(Error::LengthMismatch {
374                name: "plane size".into(),
375                expected,
376                actual,
377            });
378        }
379
380        if plane.step() != first.step() {
381            return Err(Error::LengthMismatch {
382                name: "plane step".into(),
383                expected: to_usize(first.step(), "plane step")?,
384                actual: to_usize(plane.step(), "plane step")?,
385            });
386        }
387    }
388
389    Ok(())
390}
391
392trait PlaneInfo<T> {
393    fn size(&self) -> Size;
394    fn step(&self) -> i32;
395}
396
397impl<T> PlaneInfo<T> for ImageView<'_, T, C1> {
398    fn size(&self) -> Size {
399        self.size()
400    }
401
402    fn step(&self) -> i32 {
403        self.step()
404    }
405}
406
407impl<T> PlaneInfo<T> for ImageViewMut<'_, T, C1> {
408    fn size(&self) -> Size {
409        self.size()
410    }
411
412    fn step(&self) -> i32 {
413        self.step()
414    }
415}
416
417pub(crate) fn image_descriptors<T, L>(
418    images: &[ImageView<'_, T, L>],
419) -> Vec<sys::NppiImageDescriptor>
420where
421    L: ChannelLayout,
422{
423    images.iter().map(ImageView::descriptor).collect()
424}
425
426pub(crate) fn image_descriptors_mut<T, L>(
427    images: &mut [ImageViewMut<'_, T, L>],
428) -> Vec<sys::NppiImageDescriptor>
429where
430    L: ChannelLayout,
431{
432    images.iter_mut().map(ImageViewMut::descriptor).collect()
433}