mirror of
https://github.com/l1npengtul/nokhwa.git
synced 2026-07-04 02:27:26 +00:00
preliminary browsercamera commit
This commit is contained in:
Generated
+572
-121
File diff suppressed because it is too large
Load Diff
+7
-3
@@ -30,7 +30,7 @@ input-native = ["input-avfoundation", "input-v4l", "input-msmf"]
|
||||
# Re-enable it once soundness has been proven + mozjpeg is updated to 0.9.x
|
||||
# input-uvc = ["uvc", "uvc/vendor", "usb_enumeration", "lazy_static"]
|
||||
input-opencv = ["opencv", "opencv/rgb", "rgb", "nokhwa-core/opencv-mat"]
|
||||
input-jscam = [ "wasm-bindgen-futures", "wasm-rs-async-executor", "output-async", "js-sys", "web-sys"]
|
||||
input-jscam = [ "wasm-bindgen-futures", "wasm-rs-async-executor", "output-async", "js-sys", "web-sys", "serde-wasm-bindgen", "serde"]
|
||||
output-wgpu = ["wgpu", "nokhwa-core/wgpu-types"]
|
||||
#output-wasm = ["input-jscam"]
|
||||
output-threaded = []
|
||||
@@ -73,11 +73,11 @@ version = "0.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.wgpu]
|
||||
version = "0.19"
|
||||
version = "0.20"
|
||||
optional = true
|
||||
|
||||
[dependencies.opencv]
|
||||
version = "0.89"
|
||||
version = "0.92"
|
||||
default-features = false
|
||||
features = ["videoio"]
|
||||
optional = true
|
||||
@@ -143,6 +143,10 @@ optional = true
|
||||
version = "0.9"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde-wasm-bindgen]
|
||||
version = "0.6"
|
||||
optional = true
|
||||
|
||||
[dependencies.async-trait]
|
||||
version = "0.1"
|
||||
optional = true
|
||||
|
||||
@@ -178,11 +178,11 @@ mod internal {
|
||||
}
|
||||
};
|
||||
|
||||
self.camera_format = CameraFormat::new(
|
||||
self.camera_format = Some(CameraFormat::new(
|
||||
Resolution::new(format.width, format.height),
|
||||
frame_format: ,
|
||||
fps,
|
||||
);
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
Err(why) => Err(NokhwaError::GetPropertyError {
|
||||
@@ -198,10 +198,6 @@ mod internal {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn init_with_format(&mut self, format: FormatFilter) -> Result<CameraFormat, NokhwaError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn backend(&self) -> ApiBackend {
|
||||
ApiBackend::Video4Linux
|
||||
}
|
||||
@@ -596,8 +592,8 @@ mod internal {
|
||||
|
||||
fn fourcc_to_frameformat(fourcc: FourCC) -> Option<FrameFormat> {
|
||||
match fourcc.str().ok()? {
|
||||
"YUYV" => Some(FrameFormat::Yuv422),
|
||||
"UYVY" => Some(FrameFormat::Uyv422),
|
||||
"YUYV" => Some(FrameFormat::Yuy2_422),
|
||||
"UYVY" => Some(FrameFormat::Uyvy_422),
|
||||
"YV12" => Some(FrameFormat::Yv12),
|
||||
"MJPG" => Some(FrameFormat::MJpeg),
|
||||
"GRAY" => Some(FrameFormat::Luma8),
|
||||
|
||||
@@ -21,5 +21,7 @@ core-video-sys = "0.1"
|
||||
cocoa-foundation = "0.1"
|
||||
objc = { version = "0.2", features = ["exception"] }
|
||||
block = "0.1"
|
||||
flume = "0.10"
|
||||
flume = "0.11.0"
|
||||
once_cell = "1.16"
|
||||
|
||||
av-foundation = "0.3.0"
|
||||
+11
-2
@@ -16,6 +16,7 @@ serialize = ["serde"]
|
||||
wgpu-types = ["wgpu"]
|
||||
opencv-mat = ["opencv"]
|
||||
docs-features = ["serialize", "wgpu-types"]
|
||||
conversions = ["dcv-color-primitives", "yuvutils-rs", "mozjpeg"]
|
||||
async = ["async-trait"]
|
||||
test-fail-warnings = []
|
||||
|
||||
@@ -35,11 +36,11 @@ features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.wgpu]
|
||||
version = "0.19"
|
||||
version = "22"
|
||||
optional = true
|
||||
|
||||
[dependencies.opencv]
|
||||
version = "0.89.0"
|
||||
version = "0.92"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
@@ -51,5 +52,13 @@ optional = true
|
||||
version = "0.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.dcv-color-primitives]
|
||||
version = "0.6"
|
||||
optional = true
|
||||
|
||||
[dependencies.yuvutils-rs]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["docs-features"]
|
||||
|
||||
+2
-152
@@ -14,8 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::{ types::Resolution};
|
||||
use crate::types::Resolution;
|
||||
use bytes::Bytes;
|
||||
use crate::frame_format::FrameFormat;
|
||||
|
||||
/// A buffer returned by a camera to accommodate custom decoding.
|
||||
/// Contains information of Resolution, the buffer's [`FrameFormat`], and the buffer.
|
||||
@@ -64,154 +65,3 @@ impl Buffer {
|
||||
self.source_frame_format
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "opencv-mat")]
|
||||
use crate::error::NokhwaError;
|
||||
#[cfg(feature = "opencv-mat")]
|
||||
use image::ImageBuffer;
|
||||
|
||||
#[cfg(feature = "opencv-mat")]
|
||||
impl Buffer {
|
||||
|
||||
/// Decodes a image with allocation using the provided [`FormatDecoder`].
|
||||
/// # Errors
|
||||
/// Will error when the decoding fails.
|
||||
#[inline]
|
||||
pub fn decode_image<F: FormatDecoder>(
|
||||
&self,
|
||||
) -> Result<ImageBuffer<F::Output, Vec<u8>>, NokhwaError> {
|
||||
let new_data = F::write_output(self.source_frame_format, self.resolution, &self.buffer)?;
|
||||
let image =
|
||||
ImageBuffer::from_raw(self.resolution.width_x, self.resolution.height_y, new_data)
|
||||
.ok_or(NokhwaError::ProcessFrameError {
|
||||
src: self.source_frame_format,
|
||||
destination: stringify!(F).to_string(),
|
||||
error: "Failed to create buffer".to_string(),
|
||||
})?;
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a `buffer`.
|
||||
/// # Errors
|
||||
/// Will error when the decoding fails, or the provided buffer is too small.
|
||||
#[inline]
|
||||
pub fn decode_image_to_buffer<F: FormatDecoder>(
|
||||
&self,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), NokhwaError> {
|
||||
F::write_output_buffer(
|
||||
self.source_frame_format,
|
||||
self.resolution,
|
||||
&self.buffer,
|
||||
buffer,
|
||||
)
|
||||
}
|
||||
/// Decodes a image with allocation using the provided [`FormatDecoder`] into a [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
|
||||
///
|
||||
/// Note that this does a clone when creating the buffer, to decouple the lifetime of the internal data to the temporary Buffer. If you want to avoid this, please see [`decode_opencv_mat`](Self::decode_opencv_mat).
|
||||
/// # Errors
|
||||
/// Will error when the decoding fails, or `OpenCV` failed to create/copy the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
|
||||
/// # Safety
|
||||
/// This function uses `unsafe` in order to create the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html). Please see [`Mat::new_rows_cols_with_data`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html#method.new_rows_cols_with_data) for more.
|
||||
///
|
||||
/// Most notably, the `data` **must** stay in scope for the duration of the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html) or bad, ***bad*** things happen.
|
||||
#[cfg(feature = "opencv-mat")]
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "opencv-mat")))]
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
pub fn decode_opencv_mat<F: FormatDecoder>(
|
||||
&mut self,
|
||||
) -> Result<opencv::core::Mat, NokhwaError> {
|
||||
use image::Pixel;
|
||||
use opencv::core::{Mat, Mat_AUTO_STEP, CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4};
|
||||
|
||||
let array_type = match F::Output::CHANNEL_COUNT {
|
||||
1 => CV_8UC1,
|
||||
2 => CV_8UC2,
|
||||
3 => CV_8UC3,
|
||||
4 => CV_8UC4,
|
||||
_ => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::RAWRGB,
|
||||
destination: "OpenCV Mat".to_string(),
|
||||
error: "Invalid Decoder FormatDecoder Channel Count".to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
// TODO: Look into removing this unnecessary copy.
|
||||
let mat1 = Mat::new_rows_cols_with_data(
|
||||
self.resolution.height_y as i32,
|
||||
self.resolution.width_x as i32,
|
||||
array_type,
|
||||
self.buffer.as_ref().as_ptr().cast_mut().cast(),
|
||||
Mat_AUTO_STEP,
|
||||
)
|
||||
.map_err(|why| NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::Rgb8,
|
||||
destination: "OpenCV Mat".to_string(),
|
||||
error: why.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(mat1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wgpu-types")]
|
||||
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, ImageCopyTexture, TextureAspect, ImageDataLayout};
|
||||
use crate::frame_format::FrameFormat;
|
||||
|
||||
#[cfg(feature = "wgpu-types")]
|
||||
impl Buffer {
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "wgpu-types")))]
|
||||
/// Directly copies a frame to a Wgpu texture. This will automatically convert the frame into a RGBA frame.
|
||||
/// # Errors
|
||||
/// If the frame cannot be captured or the resolution is 0 on any axis, this will error.
|
||||
fn frame_texture<'a>(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
label: Option<&'a str>,
|
||||
) -> Result<wgpu::Texture, NokhwaError> {
|
||||
let frame = self.frame()?.decode_image::<RgbAFormat>()?;
|
||||
|
||||
let texture_size = Extent3d {
|
||||
width: frame.width(),
|
||||
height: frame.height(),
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let texture = device.create_texture(&TextureDescriptor {
|
||||
label,
|
||||
size: texture_size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8UnormSrgb,
|
||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
|
||||
view_formats: &[TextureFormat::Rgba8UnormSrgb],
|
||||
});
|
||||
|
||||
let width_nonzero = 4 * frame.width();
|
||||
let height_nonzero = frame.height();
|
||||
|
||||
queue.write_texture(
|
||||
ImageCopyTexture {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
aspect: TextureAspect::All,
|
||||
},
|
||||
&frame,
|
||||
ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: width_nonzero,
|
||||
rows_per_image: height_nonzero,
|
||||
},
|
||||
texture_size,
|
||||
);
|
||||
|
||||
Ok(texture)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
use image::{ImageBuffer, Pixel};
|
||||
use crate::buffer::Buffer;
|
||||
use crate::frame_format::FrameFormat;
|
||||
|
||||
/// Trait to define a struct that can decode a [`Buffer`]
|
||||
pub trait Decoder {
|
||||
/// Formats that the decoder can decode.
|
||||
const ALLOWED_FORMATS: &'static FrameFormat;
|
||||
/// Output pixel type (e.g. [`Rgb<u8>`](image::Rgb))
|
||||
type OutputPixels: Pixel;
|
||||
|
||||
type PixelContainer;
|
||||
/// Error that the decoder will output (use [`NokhwaError`] if you're not sure)
|
||||
type Error;
|
||||
|
||||
/// Decode function.
|
||||
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, 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 [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
|
||||
|
||||
/// Decoder Predicted Size
|
||||
fn predicted_size_of_frame(&mut self, ) -> Option<usize>;
|
||||
}
|
||||
|
||||
/// 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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
fn decode_static_to_buffer(buffer: &mut [<<Self as Decoder>::OutputPixels as 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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Decoder that does not change its internal state, decoding to a user provided buffer.
|
||||
fn decode_nm_to_buffer(&self, buffer: &mut [<<Self as Decoder>::OutputPixels as 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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Asynchronous decoder to user buffer.
|
||||
async fn decode_buffer(&mut self, buffer: &mut [Self::OutputPixels::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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Asynchronous decoder to user buffer.
|
||||
async fn decode_static_buffer(buffer: &mut [Self::OutputPixels::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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Asynchronous decoder to user buffer.
|
||||
async fn decode_nm_buffer(&self, buffer: &mut [Self::OutputPixels::Subpixel]) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use dcv_color_primitives::PixelFormat;
|
||||
use image::{ExtendedColorType, ImageBuffer, Pixel, PixelWithColorType};
|
||||
use crate::buffer::Buffer;
|
||||
use crate::decoders::Decoder;
|
||||
use crate::error::NokhwaError;
|
||||
use crate::frame_format::FrameFormat;
|
||||
|
||||
pub struct GeneralPurposeDecoder<D> where D: PixelWithColorType;
|
||||
|
||||
impl<D> Decoder for GeneralPurposeDecoder<D> where D: PixelWithColorType {
|
||||
const ALLOWED_FORMATS: &'static [FrameFormat] = &[
|
||||
FrameFormat::MJpeg, FrameFormat::Luma8, FrameFormat::Luma16, FrameFormat::Rgb8, FrameFormat::RgbA8,
|
||||
FrameFormat::Nv12, FrameFormat::Nv21, FrameFormat::Uyvy_422, FrameFormat::Yuy2_422, FrameFormat::Yv12,
|
||||
FrameFormat::Yuv444, FrameFormat::I420, FrameFormat::I422, FrameFormat::I444
|
||||
];
|
||||
|
||||
type OutputPixels = D;
|
||||
type PixelContainer = Vec<D::Subpixel>;
|
||||
type Error = NokhwaError;
|
||||
|
||||
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error> {
|
||||
if !Self::ALLOWED_FORMATS.contains(&buffer.source_frame_format()) {
|
||||
return Err(NokhwaError::ConversionError(format!("Invaid frame format {} (allowed formats: {:?})", buffer.source_frame_format(), Self::ALLOWED_FORMATS)))
|
||||
}
|
||||
|
||||
let destination = match D::COLOR_TYPE {
|
||||
ExtendedColorType::Rgb8 => PixelFormat::Rgb,
|
||||
ExtendedColorType::Rgba8 => PixelFormat::Rgba,
|
||||
ExtendedColorType::Bgr8 => PixelFormat::Bgr,
|
||||
ExtendedColorType::Bgra8 => PixelFormat::Bgra,
|
||||
_ => return Err(())
|
||||
};
|
||||
|
||||
// some extra processing needed for some formats
|
||||
|
||||
let source = match buffer.source_frame_format() {
|
||||
FrameFormat::MJpeg => PixelFormat::Rgb, // => JPEG decoder
|
||||
FrameFormat::Yuy2_422 => PixelFormat::I422,
|
||||
FrameFormat::Uyvy_422 => PixelFormat::I422,
|
||||
FrameFormat::Yuv444 => PixelFormat::I444,
|
||||
FrameFormat::Nv12 => PixelFormat::Nv12,
|
||||
FrameFormat::Nv21 => PixelFormat::Nv12,
|
||||
FrameFormat::Yv12 => PixelFormat::I420,
|
||||
FrameFormat::I420 => PixelFormat::I420,
|
||||
// already decoded
|
||||
FrameFormat::Rgb8 => PixelFormat::Rgb,
|
||||
FrameFormat::RgbA8 => {
|
||||
PixelFormat::Rgba
|
||||
}
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
use std::ops::Deref;
|
||||
use image::{ExtendedColorType, ImageBuffer, Pixel};
|
||||
use crate::buffer::Buffer;
|
||||
use crate::error::NokhwaError;
|
||||
use crate::frame_format::FrameFormat;
|
||||
|
||||
/// Trait to define a struct that can decode a [`Buffer`]
|
||||
pub trait Decoder {
|
||||
/// Formats that the decoder can decode.
|
||||
const ALLOWED_FORMATS: &'static [FrameFormat];
|
||||
/// Output pixel type (e.g. [`Rgb<u8>`](image::Rgb))
|
||||
type OutputPixels: Pixel;
|
||||
|
||||
type PixelContainer: Deref<Target = [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]>;
|
||||
/// Error that the decoder will output (use [`NokhwaError`] if you're not sure)
|
||||
type Error;
|
||||
|
||||
/// Decode function.
|
||||
fn decode(&mut self, buffer: &Buffer) -> Result<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Decode to user-provided Buffer
|
||||
///
|
||||
/// Incase that the buffer is not large enough this should error.
|
||||
fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
|
||||
|
||||
/// Decoder Predicted Size
|
||||
fn predicted_size_of_frame(buffer: &Buffer) -> Option<usize> {
|
||||
if !Self::ALLOWED_FORMATS.contains(&buffer.source_frame_format()) {
|
||||
return None
|
||||
}
|
||||
let res = buffer.resolution();
|
||||
Some(res.x() as usize * res.y() as usize * core::mem::size_of::<<<Self as Decoder>::OutputPixels as Pixel>::Subpixel>() * <<Self as Decoder>::OutputPixels as Pixel>::CHANNEL_COUNT as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
fn decode_static_to_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as 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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Asynchronous decoder to user buffer.
|
||||
async fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as 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<ImageBuffer<Self::OutputPixels, Self::PixelContainer>, Self::Error>;
|
||||
|
||||
/// Asynchronous decoder to user buffer.
|
||||
async fn decode_static_buffer(&mut self, buffer: &Buffer, output: &mut [<<Self as Decoder>::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "conversions")]
|
||||
pub mod general;
|
||||
@@ -57,4 +57,6 @@ pub enum NokhwaError {
|
||||
UnsupportedOperationError(ApiBackend),
|
||||
#[error("This operation is not implemented yet: {0}")]
|
||||
NotImplementedError(String),
|
||||
#[error("Failed To Convert: {0}")]
|
||||
ConversionError(String),
|
||||
}
|
||||
|
||||
@@ -21,196 +21,90 @@ pub enum CustomFormatRequestType {
|
||||
HighestFrameRate,
|
||||
HighestResolution,
|
||||
Closest,
|
||||
Exact,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialOrd, PartialEq)]
|
||||
pub struct FormatRequest {
|
||||
resolution: Option<Range<Resolution>>,
|
||||
frame_rate: Option<Range<FrameRate>>,
|
||||
frame_format: Option<Vec<FrameFormat>>,
|
||||
req_type: Option<CustomFormatRequestType>,
|
||||
pub enum FormatRequest {
|
||||
Closest {
|
||||
resolution: Option<Range<Resolution>>,
|
||||
frame_rate: Option<Range<FrameRate>>,
|
||||
frame_format: Vec<FrameFormat>,
|
||||
},
|
||||
HighestFrameRate {
|
||||
frame_rate: Range<FrameRate>,
|
||||
frame_format: Vec<FrameFormat>,
|
||||
},
|
||||
HighestResolution {
|
||||
resolution: Range<Resolution>,
|
||||
frame_format: Vec<FrameFormat>,
|
||||
},
|
||||
Exact {
|
||||
resolution: Resolution,
|
||||
frame_rate: FrameRate,
|
||||
frame_format: Vec<FrameFormat>,
|
||||
},
|
||||
}
|
||||
|
||||
impl FormatRequest {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn with_standard_frame_formats(mut self) -> Self {
|
||||
self.append_frame_formats(&mut vec![
|
||||
FrameFormat::MJpeg,
|
||||
FrameFormat::Rgb8,
|
||||
FrameFormat::Yuv422,
|
||||
FrameFormat::Nv12,
|
||||
])
|
||||
}
|
||||
|
||||
pub fn push_frame_format(mut self, frame_format: FrameFormat) -> Self {
|
||||
match &mut self.frame_format {
|
||||
Some(ffs) => ffs.push(frame_format),
|
||||
None => self.frame_format = Some(vec![frame_format]),
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn remove_frame_format(mut self, frame_format: FrameFormat) -> Self {
|
||||
if let Some(ffs) = &mut self.frame_format {
|
||||
if let Some(idx) = ffs.iter().position(|ff| ff == &frame_format) {
|
||||
ffs.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn append_frame_formats(mut self, frame_formats: &mut Vec<FrameFormat>) -> Self {
|
||||
match &mut self.frame_format {
|
||||
Some(ffs) => ffs.append(frame_formats),
|
||||
None => self.frame_format = Some(frame_formats.clone()),
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reset_frame_formats(mut self) -> Self {
|
||||
self.frame_format = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_request_type(mut self, request_type: CustomFormatRequestType) -> Self {
|
||||
self.req_type = Some(request_type);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reset_request_type(mut self) -> Self {
|
||||
self.req_type = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_resolution_range(mut self, resolution_range: Range<Resolution>) -> Self {
|
||||
self.resolution = Some(resolution_range);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reset_resolution_range(mut self) -> Self {
|
||||
self.resolution = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_frame_rate_range(mut self, frame_rate_range: Range<FrameRate>) -> Self {
|
||||
self.frame_rate = Some(frame_rate_range);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reset_frame_rate_range(mut self) -> Self {
|
||||
self.frame_rate = None;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn satisfied_by_format(&self, format: &CameraFormat) -> bool {
|
||||
// check resolution
|
||||
let resolution_satisfied = match self.resolution {
|
||||
Some(res_range) => res_range.in_range(format.resolution()),
|
||||
None => true,
|
||||
};
|
||||
|
||||
let frame_rate_satisfied = match self.frame_rate {
|
||||
Some(fps_range) => fps_range.in_range(format.frame_rate()),
|
||||
None => true,
|
||||
};
|
||||
|
||||
let frame_format_satisfied = match &self.frame_format {
|
||||
Some(frame_formats) => frame_formats.contains(&format.format()),
|
||||
None => true,
|
||||
};
|
||||
|
||||
// we ignore custom bc that only makes sense in multiple formats
|
||||
|
||||
resolution_satisfied && frame_rate_satisfied && frame_format_satisfied
|
||||
}
|
||||
|
||||
|
||||
#[must_use]
|
||||
pub fn resolve(&self, list_of_formats: &[CameraFormat]) -> Option<CameraFormat> {
|
||||
// filter out bad results
|
||||
let mut remaining_formats = list_of_formats.iter().filter(|x| self.satisfied_by_format(*x)).copied().collect::<Vec<CameraFormat>>();
|
||||
if list_of_formats.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.req_type {
|
||||
Some(request) => {
|
||||
match request {
|
||||
CustomFormatRequestType::HighestFrameRate => {
|
||||
remaining_formats.sort_by(|a, b| {
|
||||
a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal)
|
||||
});
|
||||
Some(remaining_formats[0])
|
||||
}
|
||||
CustomFormatRequestType::HighestResolution => {
|
||||
remaining_formats.sort_by(|a, b| {
|
||||
a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal)
|
||||
});
|
||||
Some(remaining_formats[0])
|
||||
}
|
||||
CustomFormatRequestType::Closest => {
|
||||
|
||||
let mut closest_type = match (&self.frame_rate, &self.resolution) {
|
||||
(Some(_), Some(_)) => ClosestType::Both,
|
||||
(Some(_), None) => ClosestType::FrameRate,
|
||||
(None, Some(_)) => ClosestType::Resolution,
|
||||
(None, None) => ClosestType::None,
|
||||
};
|
||||
match self {
|
||||
FormatRequest::Closest { resolution, frame_rate, frame_format } => {
|
||||
let resolution_point = resolution.map(|x| x.preferred())?;
|
||||
|
||||
match closest_type {
|
||||
ClosestType::Resolution => {
|
||||
let resolution_point = match self.resolution.map(|x| x.preferred()).flatten() {
|
||||
Some(r) => r,
|
||||
None => return None,
|
||||
};
|
||||
let frame_rate_point = frame_rate.map(|x| x.preferred())?;
|
||||
// lets calcuate distance in 3 dimensions (add both resolution and frame_rate together)
|
||||
|
||||
let mut distances = remaining_formats.into_iter().map(|fmt| (fmt.resolution().distance_from(&resolution_point), fmt)).collect::<Vec<_>>();
|
||||
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or_else(|| Ordering::Equal));
|
||||
VecDeque::from(distances).pop_front().map(|x| x.1)
|
||||
}
|
||||
|
||||
ClosestType::FrameRate => {
|
||||
let frame_rate_point = match self.frame_rate.map(|x| x.preferred()).flatten() {
|
||||
Some(f) => f,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let mut distances = remaining_formats.into_iter().map(|fmt| (fmt.frame_rate().distance_from(&frame_rate_point), fmt)).collect::<Vec<_>>();
|
||||
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or_else(|| Ordering::Equal));
|
||||
VecDeque::from(distances).pop_front().map(|x| x.1)
|
||||
}
|
||||
ClosestType::Both => {
|
||||
let resolution_point = match self.resolution.map(|x| x.preferred()).flatten() {
|
||||
Some(r) => r,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let frame_rate_point = match self.frame_rate.map(|x| x.preferred()).flatten() {
|
||||
Some(f) => f,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// lets calcuate distance in 3 dimensions (add both resolution and frame_rate together)
|
||||
|
||||
let mut distances = remaining_formats.into_iter()
|
||||
.map(|fmt| {
|
||||
(fmt.frame_rate().distance_from(&frame_rate_point) + fmt.resolution().distance_from(&resolution_point) as f32, fmt)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or_else(|| Ordering::Equal));
|
||||
VecDeque::from(distances).pop_front().map(|x| x.1)
|
||||
}
|
||||
ClosestType::None => {
|
||||
Some(remaining_formats[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut distances = list_of_formats.iter()
|
||||
.filter(|x| {
|
||||
frame_format.contains(&x.format())
|
||||
})
|
||||
.map(|fmt| {
|
||||
((fmt.frame_rate() - frame_rate_point).abs() + fmt.resolution().distance_from(&resolution_point) as f32, fmt)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
|
||||
VecDeque::from(distances).pop_front().map(|x| x.1).copied()
|
||||
}
|
||||
None => {
|
||||
Some(remaining_formats[0])
|
||||
FormatRequest::HighestFrameRate { frame_rate, frame_format } => {
|
||||
let mut formats = list_of_formats.iter().filter(|x| {
|
||||
frame_format.contains(&x.format()) && frame_rate.in_range(x.frame_rate())
|
||||
}).collect::<Vec<_>>();
|
||||
formats.sort_by(|a, b| {
|
||||
match a.frame_rate().partial_cmp(&b.frame_rate()) {
|
||||
None | Some(Ordering::Equal) => a.resolution().cmp(&b.resolution()),
|
||||
Some(ord) => ord,
|
||||
}
|
||||
});
|
||||
formats.first().copied().copied()
|
||||
}
|
||||
FormatRequest::HighestResolution { resolution, frame_format } => {
|
||||
let mut formats = list_of_formats.iter().filter(|x| {
|
||||
frame_format.contains(&x.format()) && resolution.in_range(x.resolution())
|
||||
}).collect::<Vec<_>>();
|
||||
formats.sort_by(|a, b| {
|
||||
match a.resolution().partial_cmp(&b.resolution()) {
|
||||
None | Some(Ordering::Equal) => a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal),
|
||||
Some(ord) => ord,
|
||||
}
|
||||
});
|
||||
formats.first().copied().copied()
|
||||
}
|
||||
FormatRequest::Exact { resolution, frame_rate, frame_format } => {
|
||||
let mut formats = list_of_formats.iter().filter(|x| {
|
||||
frame_format.contains(&x.format()) && resolution == &x.resolution() && frame_rate == &x.frame_rate()
|
||||
}).collect::<Vec<_>>();
|
||||
formats.sort_by(|a, b| {
|
||||
match a.resolution().partial_cmp(&b.resolution()) {
|
||||
None | Some(Ordering::Equal) => a.frame_rate().partial_cmp(&b.frame_rate()).unwrap_or(Ordering::Equal),
|
||||
Some(ord) => ord,
|
||||
}
|
||||
});
|
||||
formats.first().copied().copied()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,15 +36,20 @@ pub enum FrameFormat {
|
||||
VP9,
|
||||
|
||||
// YCbCr formats
|
||||
|
||||
Yuv444,
|
||||
|
||||
// -> 422 16 BPP
|
||||
Yuv422,
|
||||
Uyv422,
|
||||
Yuy2_422,
|
||||
Uyvy_422,
|
||||
|
||||
// 420
|
||||
Nv12,
|
||||
Nv21,
|
||||
Yv12,
|
||||
I420,
|
||||
I422,
|
||||
I444,
|
||||
|
||||
// Grayscale Formats
|
||||
Luma8,
|
||||
@@ -72,8 +77,8 @@ impl FrameFormat {
|
||||
FrameFormat::XVid,
|
||||
FrameFormat::VP8,
|
||||
FrameFormat::VP9,
|
||||
FrameFormat::Yuv422,
|
||||
FrameFormat::Uyv422,
|
||||
FrameFormat::Yuy2_422,
|
||||
FrameFormat::Uyvy_422,
|
||||
FrameFormat::Nv12,
|
||||
FrameFormat::Nv21,
|
||||
FrameFormat::Yv12,
|
||||
@@ -98,8 +103,8 @@ impl FrameFormat {
|
||||
];
|
||||
|
||||
pub const CHROMA: &'static [FrameFormat] = &[
|
||||
FrameFormat::Yuv422,
|
||||
FrameFormat::Uyv422,
|
||||
FrameFormat::Yuy2_422,
|
||||
FrameFormat::Uyvy_422,
|
||||
FrameFormat::Nv12,
|
||||
FrameFormat::Nv21,
|
||||
FrameFormat::Yv12,
|
||||
@@ -121,8 +126,8 @@ impl FrameFormat {
|
||||
FrameFormat::XVid,
|
||||
FrameFormat::VP8,
|
||||
FrameFormat::VP9,
|
||||
FrameFormat::Yuv422,
|
||||
FrameFormat::Uyv422,
|
||||
FrameFormat::Yuy2_422,
|
||||
FrameFormat::Uyvy_422,
|
||||
FrameFormat::Nv12,
|
||||
FrameFormat::Nv21,
|
||||
FrameFormat::Yv12,
|
||||
@@ -147,18 +152,22 @@ pub struct PlatformSpecific {
|
||||
}
|
||||
|
||||
impl PlatformSpecific {
|
||||
#[must_use]
|
||||
pub fn new(backend: ApiBackend, format: u128) -> Self {
|
||||
Self { backend, format }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn backend(&self) -> ApiBackend {
|
||||
self.backend
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn format(&self) -> u128 {
|
||||
self.format
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn as_tuple(&self) -> (ApiBackend, u128) {
|
||||
(self.backend, self.format)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#![deny(clippy::pedantic)]
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::cast_precision_loss)]
|
||||
#![allow(clippy::cast_sign_loss)]
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![cfg_attr(feature = "test-fail-warning", deny(warnings))]
|
||||
#![cfg_attr(feature = "docs-features", feature(doc_cfg))]
|
||||
/*
|
||||
@@ -25,5 +28,5 @@ pub mod format_request;
|
||||
pub mod frame_format;
|
||||
pub mod traits;
|
||||
pub mod types;
|
||||
pub mod decoder;
|
||||
pub mod decoders;
|
||||
pub mod utils;
|
||||
|
||||
+28
-27
@@ -15,21 +15,14 @@
|
||||
*/
|
||||
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
error::NokhwaError,
|
||||
types::{
|
||||
ApiBackend, CameraControl, CameraFormat, CameraInfo, ControlValueSetter,
|
||||
KnownCameraControl, Resolution,
|
||||
},
|
||||
buffer::Buffer, error::NokhwaError, format_request::FormatRequest, types::{
|
||||
ApiBackend, CameraControl, CameraFormat, CameraIndex, CameraInfo, ControlValueSetter, KnownCameraControl, Resolution
|
||||
}
|
||||
};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
use crate::frame_format::FrameFormat;
|
||||
use crate::types::FrameRate;
|
||||
|
||||
pub trait Backend {
|
||||
const BACKEND: ApiBackend;
|
||||
}
|
||||
|
||||
/// This trait is for any backend that allows you to grab and take frames from a camera.
|
||||
/// Many of the backends are **blocking**, if the camera is occupied the library will block while it waits for it to become available.
|
||||
///
|
||||
@@ -38,9 +31,6 @@ pub trait Backend {
|
||||
/// - Behaviour can differ from backend to backend. While the Camera struct abstracts most of this away, if you plan to use the raw backend structs please read the `Quirks` section of each backend.
|
||||
/// - If you call [`stop_stream()`](CaptureTrait::stop_stream()), you will usually need to call [`open_stream()`](CaptureTrait::open_stream()) to get more frames from the camera.
|
||||
pub trait CaptureTrait {
|
||||
/// Initialize the camera, preparing it for use, with a random format (usually the first one).
|
||||
fn init(&mut self) -> Result<(), NokhwaError>;
|
||||
|
||||
/// Returns the current backend used.
|
||||
fn backend(&self) -> ApiBackend;
|
||||
|
||||
@@ -235,6 +225,14 @@ pub trait CaptureTrait {
|
||||
fn stop_stream(&mut self) -> Result<(), NokhwaError>;
|
||||
}
|
||||
|
||||
/// A trait to open the capture backend.
|
||||
pub trait OpenCaptureTrait: CaptureTrait {
|
||||
/// Opens a camera.
|
||||
/// # Errors
|
||||
/// Please see implementations for errors.
|
||||
fn open(index: &CameraIndex, camera_fmt: FormatRequest) -> Result<Self, NokhwaError> where Self: Sized;
|
||||
}
|
||||
|
||||
impl<T> From<T> for Box<dyn CaptureTrait>
|
||||
where
|
||||
T: CaptureTrait + 'static,
|
||||
@@ -247,9 +245,6 @@ where
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
pub trait AsyncCaptureTrait: CaptureTrait {
|
||||
/// Initialize the camera, preparing it for use, with a random format (usually the first one).
|
||||
async fn init_async(&mut self) -> Result<(), NokhwaError>;
|
||||
|
||||
/// Forcefully refreshes the stored camera format, bringing it into sync with "reality" (current camera state)
|
||||
/// # Errors
|
||||
/// If the camera can not get its most recent [`CameraFormat`]. this will error.
|
||||
@@ -271,16 +266,6 @@ pub trait AsyncCaptureTrait: CaptureTrait {
|
||||
fourcc: FrameFormat,
|
||||
) -> Result<HashMap<Resolution, Vec<u32>>, NokhwaError>;
|
||||
|
||||
/// Gets the compatible [`CameraFormat`] of the camera
|
||||
/// # Errors
|
||||
/// If it fails to get, this will error.
|
||||
async fn compatible_camera_formats_async(&mut self) -> Result<Vec<CameraFormat>, NokhwaError>;
|
||||
|
||||
/// A Vector of compatible [`FrameFormat`]s. Will only return 2 elements at most.
|
||||
/// # Errors
|
||||
/// This will error if the camera is not queryable or a query operation has failed. Some backends will error this out as a Unsupported Operation ([`UnsupportedOperationError`](NokhwaError::UnsupportedOperationError)).
|
||||
async fn compatible_fourcc_async(&mut self) -> Result<Vec<FrameFormat>, NokhwaError>;
|
||||
|
||||
/// Will set the current [`Resolution`]
|
||||
/// This will reset the current stream if used while stream is opened.
|
||||
///
|
||||
@@ -353,7 +338,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Capture one frame from the camera and immediately stop.
|
||||
pub trait OneShot: CaptureTrait {
|
||||
/// Captures one frame from the camera, returning a [`Buffer`]
|
||||
/// # Errors
|
||||
/// If opening the stream or closing the stream has an error, this will also fail.
|
||||
fn one_shot(&mut self) -> Result<Buffer, NokhwaError> {
|
||||
if self.is_stream_open() {
|
||||
self.frame()
|
||||
@@ -381,7 +370,19 @@ pub trait AsyncOneShot: AsyncCaptureTrait {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VirtualBackendTrait {}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
#[cfg_attr(feature = "async", async_trait::async_trait)]
|
||||
/// A trait to open the capture backend.
|
||||
pub trait AsyncOpenCaptureTrait: AsyncCaptureTrait {
|
||||
/// Opens a camera.
|
||||
/// # Errors
|
||||
/// Please see implementations for errors.
|
||||
async fn open(index: &CameraIndex, camera_fmt: FormatRequest) -> Result<Self, NokhwaError> where Self: Sized;
|
||||
}
|
||||
|
||||
|
||||
// pub trait VirtualBackendTrait {}
|
||||
|
||||
pub trait Distance<T> where T: PartialEq {
|
||||
fn distance_from(&self, other: &Self) -> T;
|
||||
|
||||
+118
-167
@@ -1,18 +1,15 @@
|
||||
use crate::{
|
||||
error::NokhwaError,
|
||||
frame_format::{FrameFormat},
|
||||
frame_format::FrameFormat,
|
||||
};
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{
|
||||
borrow::Borrow, cmp::Ordering, fmt::{
|
||||
Debug,
|
||||
Display,
|
||||
Formatter
|
||||
},
|
||||
borrow::Borrow,
|
||||
cmp::Ordering,
|
||||
hash::{Hash, Hasher}
|
||||
}, hash::{Hash, Hasher}, ops::{Add, Deref, DerefMut, Sub}
|
||||
};
|
||||
use crate::traits::Distance;
|
||||
|
||||
@@ -26,7 +23,7 @@ pub struct Range<T>
|
||||
lower_inclusive: bool,
|
||||
maximum: Option<T>,
|
||||
upper_inclusive: bool,
|
||||
preferred: Option<T>,
|
||||
preferred: T,
|
||||
}
|
||||
|
||||
impl<T> Range<T>
|
||||
@@ -34,7 +31,7 @@ where
|
||||
T: Copy + Clone + Debug + PartialOrd + PartialEq,
|
||||
{
|
||||
/// Create an upper and lower inclusive [`Range`]
|
||||
pub fn new(preferred: Option<T>, min: Option<T>, max: Option<T>) -> Self {
|
||||
pub fn new(preferred: T, min: Option<T>, max: Option<T>) -> Self {
|
||||
Self {
|
||||
minimum: min,
|
||||
lower_inclusive: true,
|
||||
@@ -45,7 +42,7 @@ where
|
||||
}
|
||||
|
||||
pub fn with_inclusive(
|
||||
preferred: Option<T>,
|
||||
preferred: T,
|
||||
min: Option<T>,
|
||||
lower_inclusive: bool,
|
||||
max: Option<T>,
|
||||
@@ -60,21 +57,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_preferred(preferred: T) -> Self {
|
||||
pub fn exact(preferred: T) -> Self {
|
||||
Self {
|
||||
minimum: None,
|
||||
lower_inclusive: true,
|
||||
maximum: None,
|
||||
upper_inclusive: true,
|
||||
preferred: Some(preferred),
|
||||
preferred,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_range(&self, item: T) -> bool {
|
||||
if let Some(pref) = self.preferred {
|
||||
if pref == item {
|
||||
return true
|
||||
}
|
||||
if item == self.preferred {
|
||||
return true
|
||||
}
|
||||
|
||||
if let Some(min) = self.minimum {
|
||||
@@ -116,11 +111,7 @@ where
|
||||
self.upper_inclusive = upper_inclusive;
|
||||
}
|
||||
pub fn set_preferred(&mut self, preferred: T) {
|
||||
self.preferred = Some(preferred);
|
||||
}
|
||||
|
||||
pub fn reset_preferred(&mut self) {
|
||||
self.preferred = None;
|
||||
self.preferred = preferred;
|
||||
}
|
||||
pub fn minimum(&self) -> Option<T> {
|
||||
self.minimum
|
||||
@@ -134,7 +125,7 @@ where
|
||||
pub fn upper_inclusive(&self) -> bool {
|
||||
self.upper_inclusive
|
||||
}
|
||||
pub fn preferred(&self) -> Option<T> {
|
||||
pub fn preferred(&self) -> T {
|
||||
self.preferred
|
||||
}
|
||||
}
|
||||
@@ -149,7 +140,7 @@ where
|
||||
lower_inclusive: true,
|
||||
maximum: None,
|
||||
upper_inclusive: true,
|
||||
preferred: None,
|
||||
preferred: T::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,7 +149,7 @@ where
|
||||
|
||||
/// Describes the index of the camera.
|
||||
/// - Index: A numbered index
|
||||
/// - String: A string, used for `IPCameras`.
|
||||
/// - String: A string, used for `IPCameras` or on the Browser as DeviceIDs.
|
||||
#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub enum CameraIndex {
|
||||
@@ -237,7 +228,6 @@ impl TryFrom<CameraIndex> for usize {
|
||||
/// Note: the [`Ord`] implementation of this struct is flipped from highest to lowest.
|
||||
/// # JS-WASM
|
||||
/// This is exported as `JSResolution`
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
|
||||
pub struct Resolution {
|
||||
@@ -245,13 +235,9 @@ pub struct Resolution {
|
||||
pub height_y: u32,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen)]
|
||||
impl Resolution {
|
||||
/// Create a new resolution from 2 image size coordinates.
|
||||
/// # JS-WASM
|
||||
/// This is exported as a constructor for [`Resolution`].
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
|
||||
pub fn new(x: u32, y: u32) -> Self {
|
||||
Resolution {
|
||||
width_x: x,
|
||||
@@ -260,20 +246,14 @@ impl Resolution {
|
||||
}
|
||||
|
||||
/// Get the width of Resolution
|
||||
/// # JS-WASM
|
||||
/// This is exported as `get_Width`.
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Width))]
|
||||
#[inline]
|
||||
pub fn width(self) -> u32 {
|
||||
self.width_x
|
||||
}
|
||||
|
||||
/// Get the height of Resolution
|
||||
/// # JS-WASM
|
||||
/// This is exported as `get_Height`.
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Height))]
|
||||
#[inline]
|
||||
pub fn height(self) -> u32 {
|
||||
self.height_y
|
||||
@@ -281,7 +261,6 @@ impl Resolution {
|
||||
|
||||
/// Get the x (width) of Resolution
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))]
|
||||
#[inline]
|
||||
pub fn x(self) -> u32 {
|
||||
self.width_x
|
||||
@@ -289,11 +268,15 @@ impl Resolution {
|
||||
|
||||
/// Get the y (height) of Resolution
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))]
|
||||
#[inline]
|
||||
pub fn y(self) -> u32 {
|
||||
self.height_y
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn aspect_ratio(&self) -> f64 {
|
||||
f64::from(self.width_x) / f64::from(self.height_y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Resolution {
|
||||
@@ -330,139 +313,95 @@ impl Distance<u32> for Resolution {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
/// The frame rate of a camera. DO NOT CONSTRUCT THIS ENUM DIRECTLY. YOU WILL VIOLATE INVARIANTS. Use [`FrameRate::new_integer`], [`FrameRate::new_fraction`], or [`FrameRate::new_float`] instead.
|
||||
pub enum FrameRate {
|
||||
/// The driver reports the frame rate as a clean integer (e.g. 30 FPS).
|
||||
Integer(u32),
|
||||
/// The driver reports the frame rate as a floating point number (e.g. 29.97 FPS)
|
||||
Float(f32),
|
||||
/// The driver reports the frame rate as a fraction (e.g. 2997/1000 FPS)
|
||||
Fraction {
|
||||
numerator: u16,
|
||||
denominator: u16,
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub struct FrameRate(pub f32);
|
||||
|
||||
impl FrameRate {
|
||||
pub fn new_integer(fps: u32) -> Result<Self, NokhwaError> {
|
||||
if fps == 0 {
|
||||
return Err(NokhwaError::StructureError { structure: "FrameRate".to_string(), error: "Framerate cannot be 0".to_string() })
|
||||
}
|
||||
|
||||
Ok(FrameRate::Integer(fps))
|
||||
#[must_use]
|
||||
pub fn new(fps: f32) -> Self {
|
||||
Self(fps)
|
||||
}
|
||||
|
||||
pub fn new_float(fps: f32) -> Result<Self, NokhwaError> {
|
||||
if fps.is_nan() || fps.is_infinite() || fps.is_sign_negative() || (fps > f32::EPSILON) {
|
||||
return Err(NokhwaError::StructureError { structure: "FrameRate".to_string(), error: "Invalid F32 FrameRate".to_string() })
|
||||
}
|
||||
|
||||
Ok(FrameRate::Float(fps))
|
||||
}
|
||||
|
||||
pub fn new_fraction(numerator: u16, denominator: u16) -> Result<Self, NokhwaError> {
|
||||
if numerator == 0 || denominator == 0 {
|
||||
return Err(NokhwaError::StructureError { structure: "FrameRate".to_string(), error: "Invalid Fraction (denominator or numerator is 0)".to_string() })
|
||||
}
|
||||
|
||||
Ok(
|
||||
FrameRate::Fraction {
|
||||
numerator,
|
||||
denominator,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_float(&self) -> f32 {
|
||||
match self {
|
||||
FrameRate::Integer(fps) => *fps as f32,
|
||||
FrameRate::Float(fps) => *fps,
|
||||
FrameRate::Fraction { numerator, denominator } => (*numerator as f32) / (*denominator as f32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_u32(&self) -> u32 {
|
||||
match self {
|
||||
FrameRate::Integer(fps) => *fps,
|
||||
FrameRate::Float(fps) => *fps as u32,
|
||||
FrameRate::Fraction { numerator, denominator } => (numerator / denominator) as u32,
|
||||
}
|
||||
#[must_use]
|
||||
pub fn frame_rate(&self) -> f32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FrameRate {
|
||||
fn default() -> Self {
|
||||
FrameRate::Integer(30)
|
||||
impl Deref for FrameRate {
|
||||
type Target = f32;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for FrameRate {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
let this_float = self.as_float();
|
||||
let other = other.as_float();
|
||||
this_float.partial_cmp(&other)
|
||||
impl DerefMut for FrameRate {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for FrameRate {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
FrameRate::Integer(i) => {
|
||||
state.write_u32(*i)
|
||||
}
|
||||
FrameRate::Float(f) => {
|
||||
state.write(f.to_string().as_bytes())
|
||||
}
|
||||
FrameRate::Fraction { denominator, numerator } => {
|
||||
state.write_u16(*denominator);
|
||||
state.write_u16(*numerator);
|
||||
}
|
||||
}
|
||||
state.write_u32(self.0.to_bits());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FrameRate {
|
||||
fn default() -> Self {
|
||||
FrameRate(30.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FrameRate {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FrameRate::Integer(fps) => write!(f, "Framerate: {fps} FPS"),
|
||||
FrameRate::Float(fps) => write!(f, "Framerate: {fps} FPS"),
|
||||
FrameRate::Fraction { .. } => {
|
||||
let as_float = self.as_float();
|
||||
write!(f, "Framerate: {as_float} FPS")
|
||||
}
|
||||
}
|
||||
write!(f, "{} FPS", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Distance<f32> for FrameRate {
|
||||
fn distance_from(&self, other: &Self) -> f32 {
|
||||
let self_as_float = self.as_float();
|
||||
let other_as_float = other.as_float();
|
||||
impl Add for FrameRate {
|
||||
type Output = FrameRate;
|
||||
|
||||
self_as_float.powi(2) + other_as_float.powi(2)
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
(self.0 + rhs.0).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for FrameRate {
|
||||
fn from(value: u32) -> Self {
|
||||
FrameRate::Integer(value)
|
||||
impl Add for &FrameRate {
|
||||
type Output = FrameRate;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
(self.0 + rhs.0).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Sub for FrameRate {
|
||||
type Output = FrameRate;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
(self.0 - rhs.0).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for &FrameRate {
|
||||
type Output = FrameRate;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
(self.0 - rhs.0).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for FrameRate {
|
||||
fn from(value: f32) -> Self {
|
||||
FrameRate::Float(value)
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u16, u16)> for FrameRate {
|
||||
fn from(value: (u16, u16)) -> Self {
|
||||
FrameRate::Fraction {
|
||||
numerator: value.0,
|
||||
denominator: value.1,
|
||||
}
|
||||
impl From<FrameRate> for f32 {
|
||||
fn from(value: FrameRate) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +490,7 @@ impl Default for CameraFormat {
|
||||
CameraFormat {
|
||||
resolution: Resolution::new(640, 480),
|
||||
format: FrameFormat::MJpeg,
|
||||
frame_rate: FrameRate::Integer(30),
|
||||
frame_rate: FrameRate(30.),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -570,7 +509,6 @@ impl Display for CameraFormat {
|
||||
/// `description` amd `misc` may contain information that may differ from backend to backend. Refer to each backend for details.
|
||||
/// `index` is a camera's index given to it by (usually) the OS usually in the order it is known to the system.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub struct CameraInfo {
|
||||
human_name: String,
|
||||
@@ -579,13 +517,11 @@ pub struct CameraInfo {
|
||||
index: CameraIndex,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = CameraInfo))]
|
||||
impl CameraInfo {
|
||||
/// Create a new [`CameraInfo`].
|
||||
/// # JS-WASM
|
||||
/// This is exported as a constructor for [`CameraInfo`].
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(constructor))]
|
||||
// OK, i just checkeed back on this code. WTF was I on when I wrote `&(impl AsRef<str> + ?Sized)` ????
|
||||
// I need to get on the same shit that my previous self was on, because holy shit that stuff is strong as FUCK!
|
||||
// Finally fixed this insanity. Hopefully I didnt torment anyone by actually putting this in a stable release.
|
||||
@@ -602,10 +538,6 @@ impl CameraInfo {
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `get_HumanReadableName`.
|
||||
#[must_use]
|
||||
#[cfg_attr(
|
||||
feature = "output-wasm",
|
||||
wasm_bindgen(getter = HumanReadableName)
|
||||
)]
|
||||
// yes, i know, unnecessary alloc this, unnecessary alloc that
|
||||
// but wasm bindgen
|
||||
pub fn human_name(&self) -> String {
|
||||
@@ -615,10 +547,6 @@ impl CameraInfo {
|
||||
/// Set the device info's human name.
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `set_HumanReadableName`.
|
||||
#[cfg_attr(
|
||||
feature = "output-wasm",
|
||||
wasm_bindgen(setter = HumanReadableName)
|
||||
)]
|
||||
pub fn set_human_name(&mut self, human_name: &str) {
|
||||
self.human_name = human_name.to_string();
|
||||
}
|
||||
@@ -627,7 +555,6 @@ impl CameraInfo {
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `get_Description`.
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Description))]
|
||||
pub fn description(&self) -> &str {
|
||||
self.description.borrow()
|
||||
}
|
||||
@@ -635,7 +562,6 @@ impl CameraInfo {
|
||||
/// Set the device info's description.
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `set_Description`.
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Description))]
|
||||
pub fn set_description(&mut self, description: &str) {
|
||||
self.description = description.to_string();
|
||||
}
|
||||
@@ -644,7 +570,6 @@ impl CameraInfo {
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `get_MiscString`.
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))]
|
||||
pub fn misc(&self) -> String {
|
||||
self.misc.clone()
|
||||
}
|
||||
@@ -652,7 +577,6 @@ impl CameraInfo {
|
||||
/// Set the device info's misc.
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `set_MiscString`.
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = MiscString))]
|
||||
pub fn set_misc(&mut self, misc: &str) {
|
||||
self.misc = misc.to_string();
|
||||
}
|
||||
@@ -661,7 +585,6 @@ impl CameraInfo {
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `get_Index`.
|
||||
#[must_use]
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index))]
|
||||
pub fn index(&self) -> &CameraIndex {
|
||||
&self.index
|
||||
}
|
||||
@@ -669,7 +592,6 @@ impl CameraInfo {
|
||||
/// Set the device info's index.
|
||||
/// # JS-WASM
|
||||
/// This is exported as a `set_Index`.
|
||||
#[cfg_attr(feature = "output-wasm", wasm_bindgen(setter = Index))]
|
||||
pub fn set_index(&mut self, index: CameraIndex) {
|
||||
self.index = index;
|
||||
}
|
||||
@@ -725,6 +647,7 @@ pub enum KnownCameraControl {
|
||||
Exposure,
|
||||
Iris,
|
||||
Focus,
|
||||
Facing,
|
||||
/// Other camera control. Listed is the ID.
|
||||
/// Wasteful, however is needed for a unified API across Windows, Linux, and MacOSX due to Microsoft's usage of GUIDs.
|
||||
///
|
||||
@@ -734,8 +657,8 @@ pub enum KnownCameraControl {
|
||||
|
||||
/// All camera controls in an array.
|
||||
#[must_use]
|
||||
pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] {
|
||||
[
|
||||
pub const fn all_known_camera_controls() -> &'static [KnownCameraControl] {
|
||||
&[
|
||||
KnownCameraControl::Brightness,
|
||||
KnownCameraControl::Contrast,
|
||||
KnownCameraControl::Hue,
|
||||
@@ -751,6 +674,7 @@ pub const fn all_known_camera_controls() -> [KnownCameraControl; 15] {
|
||||
KnownCameraControl::Exposure,
|
||||
KnownCameraControl::Iris,
|
||||
KnownCameraControl::Focus,
|
||||
KnownCameraControl::Facing,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1402,7 +1326,27 @@ impl Display for ControlValueSetter {
|
||||
}
|
||||
|
||||
/// The list of known capture backends to the library. <br>
|
||||
/// - `AUTO` is special - it tells the Camera struct to automatically choose a backend most suited for the current platform.
|
||||
/// - `Auto` - Use automatic selection.
|
||||
/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX`
|
||||
/// - `Video4Linux` - `Video4Linux2`, a linux specific backend.
|
||||
/// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use.
|
||||
/// - `MediaFoundation` - Microsoft Media Foundation, Windows only,
|
||||
/// - `OpenCv` - Uses `OpenCV` to capture. Platform agnostic.
|
||||
/// - `GStreamer` - ***DEPRECATED*** Uses `GStreamer` RTP to capture. Platform agnostic.
|
||||
/// - `Browser` - Uses browser APIs to capture from a webcam.
|
||||
pub enum SelectableBackend {
|
||||
Auto,
|
||||
Custom(&'static str),
|
||||
AVFoundation,
|
||||
Video4Linux,
|
||||
UniversalVideoClass,
|
||||
MediaFoundation,
|
||||
OpenCv,
|
||||
GStreamer,
|
||||
Browser,
|
||||
}
|
||||
|
||||
/// The list of known capture backends to the library. <br>
|
||||
/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX`
|
||||
/// - `Video4Linux` - `Video4Linux2`, a linux specific backend.
|
||||
/// - `UniversalVideoClass` - ***DEPRECATED*** Universal Video Class (please check [libuvc](https://github.com/libuvc/libuvc)). Platform agnostic, although on linux it needs `sudo` permissions or similar to use.
|
||||
@@ -1413,7 +1357,6 @@ impl Display for ControlValueSetter {
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub enum ApiBackend {
|
||||
Auto,
|
||||
Custom(&'static str),
|
||||
AVFoundation,
|
||||
Video4Linux,
|
||||
@@ -1495,7 +1438,7 @@ impl Display for ApiBackend {
|
||||
// }
|
||||
// }
|
||||
|
||||
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
|
||||
#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))]
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
|
||||
#[inline]
|
||||
fn decompress<'a>(
|
||||
@@ -1538,7 +1481,7 @@ fn decompress<'a>(
|
||||
/// # Safety
|
||||
/// This function uses `unsafe`. The caller must ensure that:
|
||||
/// - The input data is of the right size, does not exceed bounds, and/or the final size matches with the initial size.
|
||||
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
|
||||
#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))]
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
|
||||
#[inline]
|
||||
pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
@@ -1564,7 +1507,11 @@ pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "mjpeg", not(target_arch = "wasm"))))]
|
||||
|
||||
/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer.
|
||||
/// # Errors
|
||||
/// If the decoding fails (e.g. invalid MJpeg stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error.
|
||||
#[cfg(not(all(feature = "conversions", not(target_arch = "wasm32"))))]
|
||||
pub fn mjpeg_to_rgb(_data: &[u8], _rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
Err(NokhwaError::NotImplementedError(
|
||||
"Not available on WASM".to_string(),
|
||||
@@ -1574,11 +1521,11 @@ pub fn mjpeg_to_rgb(_data: &[u8], _rgba: bool) -> Result<Vec<u8>, NokhwaError> {
|
||||
/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer.
|
||||
/// # Errors
|
||||
/// If the decoding fails (e.g. invalid MJpeg stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error.
|
||||
#[cfg(all(feature = "mjpeg", not(target_arch = "wasm")))]
|
||||
#[cfg(not(all(feature = "conversions", not(target_arch = "wasm32"))))]
|
||||
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))]
|
||||
#[inline]
|
||||
pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> {
|
||||
let mut jpeg_decompress = decompress(data, rgba)?;
|
||||
let mut jpeg_decompress = mozjpeg::decompress(data, rgba)?;
|
||||
|
||||
// assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size());
|
||||
if dest.len() != jpeg_decompress.min_flat_buffer_size() {
|
||||
@@ -1601,7 +1548,11 @@ pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "mjpeg", not(target_arch = "wasm"))))]
|
||||
// TODO: deprecate?
|
||||
/// Equivalent to [`mjpeg_to_rgb`] except with a destination buffer.
|
||||
/// # Errors
|
||||
/// If the decoding fails (e.g. invalid MJpeg stream), the buffer is not large enough, or you are doing this on `WebAssembly`, this will error.
|
||||
#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))]
|
||||
pub fn buf_mjpeg_to_rgb(_data: &[u8], _dest: &mut [u8], _rgba: bool) -> Result<(), NokhwaError> {
|
||||
Err(NokhwaError::NotImplementedError(
|
||||
"Not available on WASM".to_string(),
|
||||
@@ -1632,17 +1583,17 @@ pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<()
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
if data.len() % 4 != 0 {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::Yuv422.into(),
|
||||
src: FrameFormat::Yuy2_422,
|
||||
destination: "RGB888".to_string(),
|
||||
error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)"
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
for chunk in data.chunks_exact(4) {
|
||||
let y0 = chunk[0] as f32;
|
||||
let u = chunk[1] as f32;
|
||||
let y1 = chunk[2] as f32;
|
||||
let v = chunk[3] as f32;
|
||||
let y0 = f32::from(chunk[0]);
|
||||
let u = f32::from(chunk[1]);
|
||||
let y1 = f32::from(chunk[2]);
|
||||
let v = f32::from(chunk[3]);
|
||||
|
||||
let r0 = y0 + 1.370_705 * (v - 128.);
|
||||
let g0 = y0 - 0.698_001 * (v - 128.) - 0.337_633 * (u - 128.);
|
||||
|
||||
@@ -11,7 +11,7 @@ pub fn min_max_range<N: Copy + PartialOrd + AddAssign<N> + Sized>(min: N, max: N
|
||||
break
|
||||
}
|
||||
|
||||
nums.push(counter)
|
||||
nums.push(counter);
|
||||
}
|
||||
|
||||
nums
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+82
-82
@@ -1,82 +1,82 @@
|
||||
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};
|
||||
|
||||
#[inline]
|
||||
fn decompress(
|
||||
data: &[u8],
|
||||
rgba: bool,
|
||||
) -> Result<, NokhwaError> {
|
||||
use mozjpeg::Decompress;
|
||||
|
||||
match Decompress::new_mem(data) {
|
||||
Ok(decompress) => {
|
||||
let decompressor_res = if rgba {
|
||||
decompress.rgba()
|
||||
} else {
|
||||
decompress.rgb()
|
||||
};
|
||||
match decompressor_res {
|
||||
Ok(decompressor) => Ok(decompressor),
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(why) => {
|
||||
return Err(NokhwaError::ProcessFrameError {
|
||||
src: FrameFormat::MJpeg,
|
||||
destination: "RGB888".to_string(),
|
||||
error: why.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct MJPegDecoder;
|
||||
|
||||
impl Decoder for MJPegDecoder {
|
||||
const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[SourceFrameFormat::FrameFormat(FrameFormat::MJpeg)];
|
||||
type Pixel = Rgb<u8>;
|
||||
type Container = Vec<u8>;
|
||||
type Error = NokhwaError;
|
||||
|
||||
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn predicted_size_of_frame(&mut self) -> Option<usize> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticDecoder for MJPegDecoder {
|
||||
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl IdemptDecoder for MJPegDecoder {
|
||||
fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
// use image::{ImageBuffer, Rgb};
|
||||
// use nokhwa_core::buffer::Buffer;
|
||||
// use nokhwa_core::r#mod::{Decoder, IdemptDecoder, StaticDecoder};
|
||||
// use nokhwa_core::error::NokhwaError;
|
||||
// use nokhwa_core::frame_format::{FrameFormat, SourceFrameFormat};
|
||||
//
|
||||
// #[inline]
|
||||
// fn decompress(
|
||||
// data: &[u8],
|
||||
// rgba: bool,
|
||||
// ) -> Result<, NokhwaError> {
|
||||
// use mozjpeg::Decompress;
|
||||
//
|
||||
// match Decompress::new_mem(data) {
|
||||
// Ok(decompress) => {
|
||||
// let decompressor_res = if rgba {
|
||||
// decompress.rgba()
|
||||
// } else {
|
||||
// decompress.rgb()
|
||||
// };
|
||||
// match decompressor_res {
|
||||
// Ok(decompressor) => Ok(decompressor),
|
||||
// Err(why) => {
|
||||
// return Err(NokhwaError::ProcessFrameError {
|
||||
// src: FrameFormat::MJpeg,
|
||||
// destination: "RGB888".to_string(),
|
||||
// error: why.to_string(),
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Err(why) => {
|
||||
// return Err(NokhwaError::ProcessFrameError {
|
||||
// src: FrameFormat::MJpeg,
|
||||
// destination: "RGB888".to_string(),
|
||||
// error: why.to_string(),
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// pub struct MJPegDecoder;
|
||||
//
|
||||
// impl Decoder for MJPegDecoder {
|
||||
// const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[SourceFrameFormat::FrameFormat(FrameFormat::MJpeg)];
|
||||
// type Pixel = Rgb<u8>;
|
||||
// type Container = Vec<u8>;
|
||||
// type Error = NokhwaError;
|
||||
//
|
||||
// fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn predicted_size_of_frame(&mut self) -> Option<usize> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl StaticDecoder for MJPegDecoder {
|
||||
// fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn decode_static_to_buffer(buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl IdemptDecoder for MJPegDecoder {
|
||||
// fn decode_nm(&self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
+41
-41
@@ -1,41 +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<u8>;
|
||||
type Container = Vec<u8>;
|
||||
type Error = ();
|
||||
|
||||
fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn predicted_size_of_frame(&mut self) -> Option<usize> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticDecoder for NV12Decoder {
|
||||
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, 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<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
// use image::{ImageBuffer, Rgb};
|
||||
// use nokhwa_core::buffer::Buffer;
|
||||
// use nokhwa_core::r#mod::{Decoder, IdemptDecoder, StaticDecoder};
|
||||
// use nokhwa_core::frame_format::SourceFrameFormat;
|
||||
//
|
||||
// pub struct NV12Decoder {}
|
||||
//
|
||||
// impl Decoder for NV12Decoder {
|
||||
// const ALLOWED_FORMATS: &'static [SourceFrameFormat] = &[];
|
||||
// type Pixel = Rgb<u8>;
|
||||
// type Container = Vec<u8>;
|
||||
// type Error = ();
|
||||
//
|
||||
// fn decode(&mut self, buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn predicted_size_of_frame(&mut self) -> Option<usize> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl StaticDecoder for NV12Decoder {
|
||||
// fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, 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<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
+54
-54
@@ -1,54 +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<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn predicted_size_of_frame(&mut self) -> Option<usize> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticDecoder for YUYVDecoder {
|
||||
fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, 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<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
// use image::ImageBuffer;
|
||||
// use nokhwa_core::buffer::Buffer;
|
||||
// use nokhwa_core::r#mod::{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<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn decode_buffer(&mut self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn predicted_size_of_frame(&mut self) -> Option<usize> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl StaticDecoder for YUYVDecoder {
|
||||
// fn decode_static(buffer: Buffer) -> Result<ImageBuffer<Self::Pixel, Self::Container>, 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<ImageBuffer<Self::Pixel, Self::Container>, Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn decode_nm_to_buffer(&self, buffer: &mut [Pixel::Subpixel]) -> Result<(), Self::Error> {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
-2720
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user