diff --git a/Cargo.toml b/Cargo.toml index 78c5497..4e768c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ output-wgpu = ["wgpu", "nokhwa-core/wgpu-types"] #output-wasm = ["input-jscam"] output-threaded = [] output-async = ["nokhwa-core/async", "async-trait"] -small-wasm = [] docs-only = ["input-native", "input-opencv", "input-jscam","output-wgpu", "output-threaded", "serialize"] docs-nolink = ["nokhwa-core/docs-features"] docs-features = [] @@ -43,6 +42,7 @@ test-fail-warning = [] [dependencies] thiserror = "1.0" paste = "1.0" +dcv-color-primitives = "0.5" [dependencies.nokhwa-core] version = "0.2" @@ -60,10 +60,6 @@ optional = true version = "0.24" default-features = false -[dependencies.v4l] -version = "0.13" -optional = true - [dependencies.usb_enumeration] version = "0.2" optional = true @@ -73,7 +69,7 @@ version = "0.16" optional = true [dependencies.opencv] -version = "0.80" +version = "0.82" default-features = false features = ["videoio"] optional = true diff --git a/nokhwa-core/src/buffer.rs b/nokhwa-core/src/buffer.rs index 89a85b9..f3f23e4 100644 --- a/nokhwa-core/src/buffer.rs +++ b/nokhwa-core/src/buffer.rs @@ -16,6 +16,8 @@ use crate::{frame_format::SourceFrameFormat, types::Resolution}; use bytes::Bytes; +use image::ImageBuffer; +use crate::error::NokhwaError; /// A buffer returned by a camera to accommodate custom decoding. /// Contains information of Resolution, the buffer's [`FrameFormat`], and the buffer. @@ -152,6 +154,9 @@ impl Buffer { } } +#[cfg(feature = "wgpu-types")] +use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, ImageCopyTexture, TextureAspect, ImageDataLayout}; + #[cfg(feature = "wgpu-types")] impl Buffer { #[cfg_attr(feature = "docs-features", doc(cfg(feature = "wgpu-types")))] @@ -160,12 +165,10 @@ impl Buffer { /// If the frame cannot be captured or the resolution is 0 on any axis, this will error. fn frame_texture<'a>( &mut self, - device: &WgpuDevice, - queue: &WgpuQueue, + device: &wgpu::Device, + queue: &wgpu::Queue, label: Option<&'a str>, - ) -> Result { - use crate::pixel_format::RgbAFormat; - use std::num::NonZeroU32; + ) -> Result { let frame = self.frame()?.decode_image::()?; let texture_size = Extent3d { @@ -182,17 +185,11 @@ impl Buffer { dimension: TextureDimension::D2, format: TextureFormat::Rgba8UnormSrgb, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + view_formats: &[TextureFormat::Rgba8UnormSrgb], }); - let width_nonzero = match NonZeroU32::try_from(4 * frame.width()) { - Ok(w) => Some(w), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; - - let height_nonzero = match NonZeroU32::try_from(frame.height()) { - Ok(h) => Some(h), - Err(why) => return Err(NokhwaError::ReadFrameError(why.to_string())), - }; + let width_nonzero = 4 * frame.width(); + let height_nonzero = frame.height(); queue.write_texture( ImageCopyTexture { diff --git a/nokhwa-core/src/decoder.rs b/nokhwa-core/src/decoder.rs new file mode 100644 index 0000000..270ba30 --- /dev/null +++ b/nokhwa-core/src/decoder.rs @@ -0,0 +1,76 @@ +use std::ops::Deref; +use image::{ImageBuffer, Pixel}; +use serde::de::Error; +use crate::buffer::Buffer; +use crate::frame_format::{SourceFrameFormat}; + +/// Trait to define a struct that can decode a [`Buffer`] +pub trait Decoder { + /// Formats that the decoder can decode. + const ALLOWED_FORMATS: &'static [SourceFrameFormat]; + /// Output pixel type (e.g. [`Rgb`](image::Rgb)) + type Pixel: Pixel; + /// Container for [`Self::Pixel`] - must have the same [`Pixel::Subpixel`] + type Container: Deref; + /// Error that the decoder will output (use [`NokhwaError`] if you're not sure) + type Error: Error; + + /// Decode function. + fn decode(&mut self, buffer: Buffer) -> Result, Self::Error>; + + /// Decode to user-provided Buffer + /// + /// Incase that the buffer is not large enough this should error. + fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error>; + + /// Decoder Predicted Size + fn predicted_size_of_frame(&mut self, ) -> Option; +} + +/// Decoder that can be used statically (struct contains no state) +/// +/// This is useful for times that a simple function is all that is required. +pub trait StaticDecoder: Decoder { + fn decode_static(buffer: Buffer) -> Result, Self::Error>; + + fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error>; +} + +/// Decoder that does not change its internal state. +pub trait IdemptDecoder: Decoder { + /// Decoder that does not change its internal state. + fn decode_nm(&self, buffer: Buffer) -> Result, Self::Error>; + + /// Decoder that does not change its internal state, decoding to a user provided buffer. + fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error>; +} + +#[cfg(feature = "async")] +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AsyncDecoder: Decoder { + /// Asynchronous decoder + async fn decode_async(&mut self, buffer: Buffer) -> Result, Self::Error>; + + /// Asynchronous decoder to user buffer. + async fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error>; +} + +#[cfg(feature = "async")] +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AsyncStaticDecoder: Decoder { + /// Asynchronous decoder + async fn decode_static_async(buffer: Buffer) -> Result, Self::Error>; + + /// Asynchronous decoder to user buffer. + async fn decode_static_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error>; +} + +#[cfg(feature = "async")] +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AsyncIdemptDecoder: Decoder { + /// Asynchronous decoder + async fn decode_nm_async(&self, buffer: Buffer) -> Result, Self::Error>; + + /// Asynchronous decoder to user buffer. + async fn decode_nm_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error>; +} diff --git a/nokhwa-core/src/lib.rs b/nokhwa-core/src/lib.rs index c5ea0b6..8742091 100644 --- a/nokhwa-core/src/lib.rs +++ b/nokhwa-core/src/lib.rs @@ -25,3 +25,4 @@ pub mod format_filter; pub mod frame_format; pub mod traits; pub mod types; +pub mod decoder; diff --git a/nokhwa-core/src/types.rs b/nokhwa-core/src/types.rs index ac1844b..2b45f51 100644 --- a/nokhwa-core/src/types.rs +++ b/nokhwa-core/src/types.rs @@ -1333,15 +1333,6 @@ pub fn yuyv422_predicted_size(size: usize, rgba: bool) -> usize { (size / 4) * (2 * pixel_size) } -// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2 -// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB -// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte -// The YUY2(Yuv422) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888. -// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4) -// then it is converted to 6 bytes (2 pixels) of RGB888 -/// Converts a Yuv422 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) -/// # Errors -/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index. #[inline] pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { let capacity = yuyv422_predicted_size(data.len(), rgba); diff --git a/src/decoders/mjpeg.rs b/src/decoders/mjpeg.rs new file mode 100644 index 0000000..ea86b42 --- /dev/null +++ b/src/decoders/mjpeg.rs @@ -0,0 +1,30 @@ +use image::{ImageBuffer, Rgb}; +use nokhwa_core::buffer::Buffer; +use nokhwa_core::decoder::{Decoder, IdemptDecoder, StaticDecoder}; +use nokhwa_core::error::NokhwaError; +use nokhwa_core::frame_format::{FrameFormat, SourceFrameFormat}; + +pub struct MJPegDecoder; + +impl Decoder for MJPegDecoder { + const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[SourceFrameFormat::FrameFormat(FrameFormat::MJpeg)]; + type Pixel = Rgb; + type Container = Vec; + type Error = NokhwaError; + + fn decode(&mut self, buffer: Buffer) -> Result, Self::Error> { + todo!() + } +} + +impl StaticDecoder for MJPegDecoder { + fn decode_static(buffer: Buffer) -> Result, Self::Error> { + todo!() + } +} + +impl IdemptDecoder for MJPegDecoder { + fn decode_nm(buffer: Buffer) -> Result, Self::Error> { + todo!() + } +} diff --git a/src/decoders/mod.rs b/src/decoders/mod.rs new file mode 100644 index 0000000..e321a45 --- /dev/null +++ b/src/decoders/mod.rs @@ -0,0 +1,3 @@ +pub mod mjpeg; +pub mod yuyv; +pub mod nv12; diff --git a/src/decoders/nv12.rs b/src/decoders/nv12.rs new file mode 100644 index 0000000..618102b --- /dev/null +++ b/src/decoders/nv12.rs @@ -0,0 +1,41 @@ +use image::{ImageBuffer, Rgb}; +use nokhwa_core::buffer::Buffer; +use nokhwa_core::decoder::{Decoder, IdemptDecoder, StaticDecoder}; +use nokhwa_core::frame_format::SourceFrameFormat; + +pub struct NV12Decoder {} + +impl Decoder for NV12Decoder { + const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[]; + type Pixel = Rgb; + type Container = Vec; + type Error = (); + + fn decode(&mut self, buffer: Buffer) -> Result, Self::Error> { + todo!() + } + + fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> { + todo!() + } + + fn predicted_size_of_frame(&mut self) -> Option { + todo!() + } +} + +impl StaticDecoder for NV12Decoder { + fn decode_static(buffer: Buffer) -> Result, Self::Error> { + todo!() + } + + fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> { + todo!() + } +} + +impl IdemptDecoder for NV12Decoder { + fn decode_nm(buffer: Buffer) -> Result, Self::Error> { + todo!() + } +} \ No newline at end of file diff --git a/src/decoders/yuyv.rs b/src/decoders/yuyv.rs new file mode 100644 index 0000000..4202bbe --- /dev/null +++ b/src/decoders/yuyv.rs @@ -0,0 +1,54 @@ +use image::ImageBuffer; +use nokhwa_core::buffer::Buffer; +use nokhwa_core::decoder::{Decoder, IdemptDecoder, StaticDecoder}; +use nokhwa_core::frame_format::SourceFrameFormat; + +// For those maintaining this, I recommend you read: https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering#yuy2 +// https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB +// and this too: https://stackoverflow.com/questions/16107165/convert-from-yuv-420-to-imagebgr-byte +// The YUY2(Yuv422) format is a 16 bit format. We read 4 bytes at a time to get 6 bytes of RGB888. +// First, the YUY2 is converted to YCbCr 4:4:4 (4:2:2 -> 4:4:4) +// then it is converted to 6 bytes (2 pixels) of RGB888 +/// Converts a Yuv422 4:2:2 datastream to a RGB888 Stream. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) +/// # Errors +/// This may error when the data stream size is not divisible by 4, a i32 -> u8 conversion fails, or it fails to read from a certain index. +pub struct YUYVDecoder {} + +impl Decoder for YUYVDecoder { + const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[]; + type Pixel = (); + type Container = (); + type Error = (); + + fn decode(&mut self, buffer: Buffer) -> Result, Self::Error> { + todo!() + } + + fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> { + todo!() + } + + fn predicted_size_of_frame(&mut self) -> Option { + todo!() + } +} + +impl StaticDecoder for YUYVDecoder { + fn decode_static(buffer: Buffer) -> Result, Self::Error> { + todo!() + } + + fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> { + todo!() + } +} + +impl IdemptDecoder for YUYVDecoder { + fn decode_nm(&self, buffer: Buffer) -> Result, Self::Error> { + todo!() + } + + fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> { + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs index e95208b..0620e56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ mod query; #[cfg(feature = "output-threaded")] #[cfg_attr(feature = "docs-features", doc(cfg(feature = "output-threaded")))] pub mod threaded; +pub mod decoders; pub use camera::Camera; pub use init::*;