Files
nokhwa/src/utils.rs
T
2022-05-11 13:49:05 +08:00

1308 lines
41 KiB
Rust

/*
* Copyright 2021 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::NokhwaError;
use std::{
borrow::{Borrow, Cow},
cmp::Ordering,
fmt::{Display, Formatter},
};
#[cfg(feature = "output-wasm")]
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
use nokhwa_bindings_macos::avfoundation::{
AVCaptureDeviceDescriptor, AVFourCC, AVVideoResolution, CaptureDeviceFormatDescriptor,
};
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
use nokhwa_bindings_windows::{
MFCameraFormat, MFControl, MFFrameFormat, MFResolution, MediaFoundationControls,
MediaFoundationDeviceDescriptor,
};
#[cfg(feature = "input-uvc")]
use uvc::StreamFormat;
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
use v4l::{control::Description, Format, FourCC};
/// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`.
/// - YUYV is a mathematical color space. You can read more [here.](https://en.wikipedia.org/wiki/YCbCr)
/// - MJPEG is a motion-jpeg compressed frame, it allows for high frame rates.
/// # JS-WASM
/// This is exported as `FrameFormat`
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum FrameFormat {
MJPEG,
YUYV,
}
impl Display for FrameFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameFormat::MJPEG => {
write!(f, "MJPEG")
}
FrameFormat::YUYV => {
write!(f, "YUYV")
}
}
}
}
#[cfg(feature = "input-uvc")]
impl From<FrameFormat> for uvc::FrameFormat {
fn from(ff: FrameFormat) -> Self {
match ff {
FrameFormat::MJPEG => uvc::FrameFormat::MJPEG,
FrameFormat::YUYV => uvc::FrameFormat::YUYV,
}
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<MFFrameFormat> for FrameFormat {
fn from(mf_ff: MFFrameFormat) -> Self {
match mf_ff {
MFFrameFormat::MJPEG => FrameFormat::MJPEG,
MFFrameFormat::YUYV => FrameFormat::YUYV,
}
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<FrameFormat> for MFFrameFormat {
fn from(ff: FrameFormat) -> Self {
match ff {
FrameFormat::MJPEG => MFFrameFormat::MJPEG,
FrameFormat::YUYV => MFFrameFormat::YUYV,
}
}
}
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
impl From<AVFourCC> for FrameFormat {
fn from(av_fcc: AVFourCC) -> Self {
match av_fcc {
AVFourCC::YUV2 => FrameFormat::YUYV,
AVFourCC::MJPEG => FrameFormat::MJPEG,
}
}
}
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
impl From<FrameFormat> for AVFourCC {
fn from(ff: FrameFormat) -> Self {
match ff {
FrameFormat::MJPEG => AVFourCC::MJPEG,
FrameFormat::YUYV => AVFourCC::YUV2,
}
}
}
/// Describes a Resolution.
/// This struct consists of a Width and a Height value (x,y). <br>
/// 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(js_name = JSResolution))]
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
pub struct Resolution {
pub width_x: u32,
pub height_y: u32,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSResolution))]
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,
height_y: y,
}
}
/// Get the width of Resolution
/// # JS-WASM
/// This is exported as `get_Width`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Width))]
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))]
pub fn height(self) -> u32 {
self.height_y
}
/// Get the x (width) of Resolution
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))]
pub fn x(self) -> u32 {
self.width_x
}
/// Get the y (height) of Resolution
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(skip))]
pub fn y(self) -> u32 {
self.height_y
}
}
impl Display for Resolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}x{}", self.x(), self.y())
}
}
impl PartialOrd for Resolution {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Resolution {
fn cmp(&self, other: &Self) -> Ordering {
match self.x().cmp(&other.x()) {
Ordering::Less => Ordering::Less,
Ordering::Equal => self.y().cmp(&other.y()),
Ordering::Greater => Ordering::Greater,
}
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<MFResolution> for Resolution {
fn from(mf_res: MFResolution) -> Self {
Resolution {
width_x: mf_res.width_x,
height_y: mf_res.height_y,
}
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<Resolution> for MFResolution {
fn from(res: Resolution) -> Self {
MFResolution {
width_x: res.width(),
height_y: res.height(),
}
}
}
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
#[allow(clippy::cast_sign_loss)]
impl From<AVVideoResolution> for Resolution {
fn from(res: AVVideoResolution) -> Self {
Resolution {
width_x: res.width as u32,
height_y: res.height as u32,
}
}
}
/// This is a convenience struct that holds all information about the format of a webcam stream.
/// It consists of a [`Resolution`], [`FrameFormat`], and a frame rate(u8).
#[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)]
pub struct CameraFormat {
resolution: Resolution,
format: FrameFormat,
frame_rate: u32,
}
impl CameraFormat {
/// Construct a new [`CameraFormat`]
#[must_use]
pub fn new(resolution: Resolution, format: FrameFormat, frame_rate: u32) -> Self {
CameraFormat {
resolution,
format,
frame_rate,
}
}
/// [`CameraFormat::new()`], but raw.
#[must_use]
pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: u32) -> Self {
CameraFormat {
resolution: Resolution {
width_x: res_x,
height_y: res_y,
},
format,
frame_rate: fps,
}
}
/// Get the resolution of the current [`CameraFormat`]
#[must_use]
pub fn resolution(&self) -> Resolution {
self.resolution
}
/// Get the width of the resolution of the current [`CameraFormat`]
#[must_use]
pub fn width(&self) -> u32 {
self.resolution.width()
}
/// Get the height of the resolution of the current [`CameraFormat`]
#[must_use]
pub fn height(&self) -> u32 {
self.resolution.height()
}
/// Set the [`CameraFormat`]'s resolution.
pub fn set_resolution(&mut self, resolution: Resolution) {
self.resolution = resolution;
}
/// Get the frame rate of the current [`CameraFormat`]
#[must_use]
pub fn frame_rate(&self) -> u32 {
self.frame_rate
}
/// Set the [`CameraFormat`]'s frame rate.
pub fn set_frame_rate(&mut self, frame_rate: u32) {
self.frame_rate = frame_rate;
}
/// Get the [`CameraFormat`]'s format.
#[must_use]
pub fn format(&self) -> FrameFormat {
self.format
}
/// Set the [`CameraFormat`]'s format.
pub fn set_format(&mut self, format: FrameFormat) {
self.format = format;
}
}
#[cfg(feature = "input-uvc")]
impl From<CameraFormat> for StreamFormat {
fn from(cf: CameraFormat) -> Self {
StreamFormat {
width: cf.width(),
height: cf.height(),
fps: cf.frame_rate(),
format: cf.format().into(),
}
}
}
impl Default for CameraFormat {
fn default() -> Self {
CameraFormat {
resolution: Resolution::new(640, 480),
format: FrameFormat::MJPEG,
frame_rate: 30,
}
}
}
impl Display for CameraFormat {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}@{}FPS, {} Format",
self.resolution, self.frame_rate, self.format
)
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<MFCameraFormat> for CameraFormat {
fn from(mf_cam_fmt: MFCameraFormat) -> Self {
CameraFormat {
resolution: mf_cam_fmt.resolution().into(),
format: mf_cam_fmt.format().into(),
frame_rate: mf_cam_fmt.frame_rate(),
}
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<CameraFormat> for MFCameraFormat {
fn from(cf: CameraFormat) -> Self {
MFCameraFormat::new(cf.resolution.into(), cf.format.into(), cf.frame_rate)
}
}
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
impl From<CameraFormat> for Format {
fn from(cam_fmt: CameraFormat) -> Self {
let pxfmt = match cam_fmt.format() {
FrameFormat::MJPEG => FourCC::new(b"MJPG"),
FrameFormat::YUYV => FourCC::new(b"YUYV"),
};
Format::new(cam_fmt.width(), cam_fmt.height(), pxfmt)
}
}
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::cast_lossless)]
impl From<CameraFormat> for CaptureDeviceFormatDescriptor {
fn from(cf: CameraFormat) -> Self {
CaptureDeviceFormatDescriptor {
resolution: AVVideoResolution {
width: cf.width() as i32,
height: cf.height() as i32,
},
fps: cf.frame_rate(),
fourcc: cf.format().into(),
}
}
}
/// Information about a Camera e.g. its name.
/// `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.
/// # JS-WASM
/// This is exported as a `JSCameraInfo`.
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_name = JSCameraInfo))]
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub struct CameraInfo {
human_name: String,
description: String,
misc: String,
index: CameraIndex,
}
#[cfg_attr(feature = "output-wasm", wasm_bindgen(js_class = JSCameraInfo))]
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))]
pub fn new(
human_name: &(impl AsRef<str> + ?Sized),
description: &(impl AsRef<str> + ?Sized),
misc: &(impl AsRef<str> + ?Sized),
index: CameraIndex,
) -> Self {
CameraInfo {
human_name: human_name.as_ref().to_string(),
description: description.as_ref().to_string(),
misc: misc.as_ref().to_string(),
index,
}
}
/// Get a reference to the device info's human readable name.
/// # JS-WASM
/// This is exported as a `get_HumanReadableName`.
#[must_use]
#[cfg_attr(
feature = "output-wasm",
wasm_bindgen(getter = HumanReadableName)
)]
pub fn human_name(&self) -> &'_ str {
self.human_name.borrow()
}
/// 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<S: AsRef<str>>(&mut self, human_name: S) {
self.human_name = human_name.as_ref().to_string();
}
/// Get a reference to the device info's description.
/// # 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()
}
/// 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<S: AsRef<str>>(&mut self, description: S) {
self.description = description.as_ref().to_string();
}
/// Get a reference to the device info's misc.
/// # JS-WASM
/// This is exported as a `get_MiscString`.
#[must_use]
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = MiscString))]
pub fn misc(&self) -> &'_ str {
self.misc.borrow()
}
/// 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<S: AsRef<str>>(&mut self, misc: S) {
self.misc = misc.as_ref().to_string();
}
/// Get a reference to the device info's index.
/// # 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
}
/// 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;
}
/// Gets the device info's index as an `u32`.
/// # Errors
/// If the index is not parsable as a `u32`, this will error.
/// # JS-WASM
/// This is exported as `get_Index_Int`
#[cfg_attr(feature = "output-wasm", wasm_bindgen(getter = Index_Int))]
pub fn index_num(&self) -> Result<u32, NokhwaError> {
match &self.index {
CameraIndex::Index(i) => Ok(*i),
CameraIndex::String(s) => match s.parse::<u32>() {
Ok(p) => Ok(p),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "index-int".to_string(),
error: why.to_string(),
}),
},
}
}
}
impl Display for CameraInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Name: {}, Description: {}, Extra: {}, Index: {}",
self.human_name, self.description, self.misc, self.index
)
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<MediaFoundationDeviceDescriptor<'_>> for CameraInfo {
fn from(dev_desc: MediaFoundationDeviceDescriptor<'_>) -> Self {
CameraInfo {
human_name: dev_desc,
description: "Media Foundation Device",
misc: dev_desc.link_as_string(),
index: CameraIndex::Index(dev_desc.index() as u32),
}
}
}
#[cfg(any(
all(
feature = "input-avfoundation",
any(target_os = "macos", target_os = "ios")
),
all(
feature = "docs-only",
feature = "docs-nolink",
feature = "input-avfoundation"
)
))]
#[allow(clippy::cast_possible_truncation)]
impl From<AVCaptureDeviceDescriptor> for CameraInfo {
fn from(descriptor: AVCaptureDeviceDescriptor) -> Self {
CameraInfo {
human_name: descriptor.name,
description: descriptor.description,
misc: descriptor.misc,
index: CameraIndex::Index(descriptor.index as u32),
}
}
}
/// The list of known camera controls to the library. <br>
/// These can control the picture brightness, etc. <br>
/// Note that not all backends/devices support all these. Run [`supported_camera_controls()`](crate::CaptureBackendTrait::supported_camera_controls) to see which ones can be set.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum KnownCameraControls {
Brightness,
Contrast,
Hue,
Saturation,
Sharpness,
Gamma,
ColorEnable,
WhiteBalance,
BacklightComp,
Gain,
Pan,
Tilt,
Roll,
Zoom,
Exposure,
Iris,
Focus,
}
/// All camera controls in an array.
#[must_use]
pub fn all_known_camera_controls() -> [KnownCameraControls; 17] {
[
KnownCameraControls::Brightness,
KnownCameraControls::Contrast,
KnownCameraControls::Hue,
KnownCameraControls::Saturation,
KnownCameraControls::Sharpness,
KnownCameraControls::Gamma,
KnownCameraControls::ColorEnable,
KnownCameraControls::WhiteBalance,
KnownCameraControls::BacklightComp,
KnownCameraControls::Gain,
KnownCameraControls::Pan,
KnownCameraControls::Tilt,
KnownCameraControls::Roll,
KnownCameraControls::Zoom,
KnownCameraControls::Exposure,
KnownCameraControls::Iris,
KnownCameraControls::Focus,
]
}
impl Display for KnownCameraControls {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", &self)
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<MediaFoundationControls> for KnownCameraControls {
fn from(mf_c: MediaFoundationControls) -> Self {
match mf_c {
MediaFoundationControls::Brightness => KnownCameraControls::Brightness,
MediaFoundationControls::Contrast => KnownCameraControls::Contrast,
MediaFoundationControls::Hue => KnownCameraControls::Hue,
MediaFoundationControls::Saturation => KnownCameraControls::Saturation,
MediaFoundationControls::Sharpness => KnownCameraControls::Sharpness,
MediaFoundationControls::Gamma => KnownCameraControls::Gamma,
MediaFoundationControls::ColorEnable => KnownCameraControls::ColorEnable,
MediaFoundationControls::WhiteBalance => KnownCameraControls::WhiteBalance,
MediaFoundationControls::BacklightComp => KnownCameraControls::BacklightComp,
MediaFoundationControls::Gain => KnownCameraControls::Gain,
MediaFoundationControls::Pan => KnownCameraControls::Pan,
MediaFoundationControls::Tilt => KnownCameraControls::Tilt,
MediaFoundationControls::Roll => KnownCameraControls::Roll,
MediaFoundationControls::Zoom => KnownCameraControls::Zoom,
MediaFoundationControls::Exposure => KnownCameraControls::Exposure,
MediaFoundationControls::Iris => KnownCameraControls::Iris,
MediaFoundationControls::Focus => KnownCameraControls::Focus,
}
}
}
#[cfg(any(
all(feature = "input-msmf", target_os = "windows"),
all(feature = "docs-only", feature = "docs-nolink", feature = "input-msmf")
))]
impl From<MFControl> for KnownCameraControls {
fn from(mf_cc: MFControl) -> Self {
mf_cc.control().into()
}
}
#[cfg(all(feature = "input-v4l", target_os = "linux"))]
impl std::convert::TryFrom<Description> for KnownCameraControls {
type Error = NokhwaError;
fn try_from(value: Description) -> Result<Self, Self::Error> {
Ok(match value.id {
9_963_776 => KnownCameraControls::Brightness,
9_963_777 => KnownCameraControls::Contrast,
9_963_779 => KnownCameraControls::Hue,
9_963_778 => KnownCameraControls::Saturation,
9_963_803 => KnownCameraControls::Sharpness,
9_963_792 => KnownCameraControls::Gamma,
9_963_802 => KnownCameraControls::WhiteBalance,
9_963_804 => KnownCameraControls::BacklightComp,
9_963_795 => KnownCameraControls::Gain,
10_094_852 => KnownCameraControls::Pan,
10_094_853 => KnownCameraControls::Tilt,
10_094_862 => KnownCameraControls::Zoom,
10_094_850 => KnownCameraControls::Exposure,
10_094_866 => KnownCameraControls::Iris,
10_094_859 => KnownCameraControls::Focus,
_ => {
return Err(NokhwaError::NotImplementedError(
"Control not implemented!".to_string(),
))
}
})
}
}
/// This tells you weather a [`KnownCameraControls`] is automatically managed by the OS/Driver
/// or manually managed by you, the programmer.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum KnownCameraControlFlag {
Automatic,
Manual,
}
impl Display for KnownCameraControlFlag {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
/// This struct tells you everything about a particular [`KnownCameraControls`]. <br>
/// However, you should never need to instantiate this struct, since its usually generated for you by `nokhwa`.
/// The only time you should be modifying this struct is when you need to set a value and pass it back to the camera.
/// NOTE: Assume the values for `min` and `max` as **non-inclusive**!.
/// E.g. if the [`CameraControl`] says `min` is 100, the minimum is actually 101.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct CameraControl {
control: KnownCameraControls,
min: i32,
max: i32,
value: i32,
step: i32,
default: i32,
flag: KnownCameraControlFlag,
active: bool,
}
impl CameraControl {
/// Creates a new [`CameraControl`]
/// # Errors
/// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error
#[allow(clippy::too_many_arguments)]
pub fn new(
control: KnownCameraControls,
minimum: i32,
maximum: i32,
value: i32,
step: i32,
default: i32,
flag: KnownCameraControlFlag,
active: bool,
) -> Result<Self, NokhwaError> {
if value >= maximum {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Value too large".to_string(),
});
}
if value <= minimum {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Value too low".to_string(),
});
}
if value % step != 0 {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Not aligned with step".to_string(),
});
}
Ok(CameraControl {
control,
min: minimum,
max: maximum,
value,
step,
default,
flag,
active,
})
}
/// Gets the [`KnownCameraControls`] of this [`CameraControl`]
#[must_use]
pub fn control(&self) -> KnownCameraControls {
self.control
}
/// Gets the minimum value of this [`CameraControl`]
#[must_use]
pub fn minimum_value(&self) -> i32 {
self.min
}
/// Gets the maximum value of this [`CameraControl`]
#[must_use]
pub fn maximum_value(&self) -> i32 {
self.max
}
/// Gets the current value of this [`CameraControl`]
#[must_use]
pub fn value(&self) -> i32 {
self.value
}
/// Sets the value of this [`CameraControl`]
/// # Errors
/// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error
pub fn set_value(&mut self, value: i32) -> Result<(), NokhwaError> {
if value >= self.maximum_value() {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Value too large".to_string(),
});
}
if value <= self.minimum_value() {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Value too low".to_string(),
});
}
if value == self.minimum_value() || value == self.maximum_value() {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Values not inclusive".to_string(),
});
}
if value % self.step() != 0 {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Not aligned with step".to_string(),
});
}
self.value = value;
Ok(())
}
/// Creates a new [`CameraControl`] but with `value`
/// # Errors
/// If the `value` is below `min`, above `max`, or is not divisible by `step`, this will error
pub fn with_value(self, value: i32) -> Result<Self, NokhwaError> {
if value >= self.maximum_value() {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Value too large".to_string(),
});
}
if value <= self.minimum_value() {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Value too low".to_string(),
});
}
if value % self.step() != 0 {
return Err(NokhwaError::StructureError {
structure: "CameraControl".to_string(),
error: "Not aligned with step".to_string(),
});
}
Ok(CameraControl {
control: self.control(),
min: self.minimum_value(),
max: self.maximum_value(),
value,
step: self.step(),
default: self.default(),
flag: self.flag(),
active: true,
})
}
/// Gets the step value of this [`CameraControl`]
/// Note that `value` must be divisible by `step`
#[must_use]
pub fn step(&self) -> i32 {
self.step
}
/// Gets the default value of this [`CameraControl`]
#[must_use]
pub fn default(&self) -> i32 {
self.default
}
/// Gets the [`KnownCameraControlFlag`] of this [`CameraControl`],
/// telling you weather this control is automatically set or manually set.
#[must_use]
pub fn flag(&self) -> KnownCameraControlFlag {
self.flag
}
/// Gets `active` of this [`CameraControl`],
/// telling you weather this control is currently active(in-use).
#[must_use]
pub fn active(&self) -> bool {
self.active
}
/// Gets `active` of this [`CameraControl`],
/// telling you weather this control is currently active(in-use).
pub fn set_active(&mut self, active: bool) {
self.active = active;
}
/// Returns a list of i32s that are valid to be set.
#[allow(clippy::cast_sign_loss)]
#[must_use]
pub fn valid_values(&self) -> Vec<i32> {
(self.minimum_value()..=self.maximum_value())
.step_by(self.step() as usize)
.into_iter()
.collect()
}
}
impl Display for CameraControl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Name: {}, Range: ({}~{}), Current: {}, Step: {}, Default: {}, Flag: {}, Active: {}",
self.control,
self.min,
self.max,
self.value,
self.step,
self.default,
self.flag,
self.active
)
}
}
impl PartialOrd for CameraControl {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CameraControl {
fn cmp(&self, other: &Self) -> Ordering {
self.control().cmp(&other.control())
}
}
/// 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.
/// - `AVFoundation` - Uses `AVFoundation` on `MacOSX`
/// - `V4L2` - `Video4Linux2`, a linux specific backend.
/// - `UVC` - 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` - Uses `GStreamer` RTP to capture. Platform agnostic.
/// - `Network` - Uses `OpenCV` to capture from an IP.
/// - `Browser` - Uses browser APIs to capture from a webcam.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub enum CaptureAPIBackend {
Auto,
AVFoundation,
Video4Linux,
UniversalVideoClass,
MediaFoundation,
OpenCv,
GStreamer,
Network,
Browser,
}
impl Display for CaptureAPIBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let self_str = format!("{:?}", self);
write!(f, "{}", self_str)
}
}
/// A webcam index that supports both strings and integers. Most backends take an int, but `IPCamera`s take a URL (string).
#[derive(Clone, Debug, Hash, PartialEq, PartialOrd)]
pub enum CameraIndex {
Index(u32),
String(String),
}
impl CameraIndex {
/// Gets the device info's index as an `u32`.
/// # Errors
/// If the index is not parsable as a `u32`, this will error.
pub fn as_index(&self) -> Result<u32, NokhwaError> {
match self {
CameraIndex::Index(i) => Ok(*i),
CameraIndex::String(s) => match s.parse::<u32>() {
Ok(p) => Ok(p),
Err(why) => Err(NokhwaError::GetPropertyError {
property: "index-int".to_string(),
error: why.to_string(),
}),
},
}
}
}
impl Display for CameraIndex {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CameraIndex::Index(idx) => {
write!(f, "{}", idx)
}
CameraIndex::String(ip) => {
write!(f, "{}", ip)
}
}
}
}
impl From<u32> for CameraIndex {
fn from(v: u32) -> Self {
CameraIndex::Index(v)
}
}
/// Trait for strings that can be converted to [`CameraIndex`]es.
pub trait ValidString: AsRef<str> {}
impl ValidString for String {}
impl<'a> ValidString for &'a String {}
impl<'a> ValidString for &'a mut String {}
impl<'a> ValidString for Cow<'a, str> {}
impl<'a> ValidString for &'a Cow<'a, str> {}
impl<'a> ValidString for &'a mut Cow<'a, str> {}
impl<'a> ValidString for &'a str {}
impl<'a> ValidString for &'a mut str {}
impl<T> From<T> for CameraIndex
where
T: ValidString,
{
fn from(v: T) -> Self {
CameraIndex::String(v.as_ref().to_string())
}
}
/// Converts a MJPEG stream of [u8] into a Vec<u8> of RGB888. (R,G,B,R,G,B,...)
/// # Errors
/// If `mozjpeg` fails to read scanlines or setup the decompressor, this will error.
/// # 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 = "decoding", not(target_arch = "wasm")))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))]
pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
use mozjpeg::Decompress;
let mut jpeg_decompress = match Decompress::new_mem(data) {
Ok(decompress) => {
let decompressor_res = if rgba {
decompress.rgba()
} else {
decompress.rgb()
};
match decompressor_res {
Ok(decompressor) => 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(),
})
}
};
let scanlines_res: Option<Vec<u8>> = jpeg_decompress.read_scanlines_flat();
assert!(jpeg_decompress.finish_decompress());
match scanlines_res {
Some(pixels) => Ok(pixels),
None => Err(NokhwaError::ProcessFrameError {
src: FrameFormat::MJPEG,
destination: "RGB888".to_string(),
error: "Failed to get read readlines into RGB888 pixels!".to_string(),
}),
}
}
#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))]
pub fn mjpeg_to_rgb(data: &[u8], rgba: bool) -> Result<Vec<u8>, NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not available on WASM".to_string(),
))
}
#[cfg(all(feature = "decoding", not(target_arch = "wasm")))]
#[cfg_attr(feature = "docs-features", doc(cfg(feature = "decoding")))]
pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> {
use mozjpeg::Decompress;
let mut jpeg_decompress = match Decompress::new_mem(data) {
Ok(decompress) => {
let decompressor_res = if rgba {
decompress.rgba()
} else {
decompress.rgb()
};
match decompressor_res {
Ok(decompressor) => 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(),
})
}
};
assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size());
jpeg_decompress.read_scanlines_flat_into(dest);
assert!(jpeg_decompress.finish_decompress());
Ok(())
}
#[cfg(not(all(feature = "decoding", not(target_arch = "wasm"))))]
pub fn buf_mjpeg_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> {
Err(NokhwaError::NotImplementedError(
"Not available on WASM".to_string(),
))
}
// 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(YUYV) 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 YUYV 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<Vec<u8>, NokhwaError> {
if data.len() % 4 != 0 {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)"
.to_string(),
});
}
let pixel_size = if rgba { 4 } else { 3 };
// yuyv yields 2 3-byte pixels per yuyv chunk
let rgb_buf_size = (data.len() / 4) * (2 * pixel_size);
let mut dest = vec![0; rgb_buf_size];
buf_yuyv422_to_rgb(data, &mut dest, rgba)?;
Ok(dest)
}
/// Same as yuyv422_to_rgb(&data) but with a destination buffer
pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> {
if data.len() % 4 != 0 {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: "Assertion failure, the YUV stream isn't 4:2:2! (wrong number of bytes)"
.to_string(),
});
}
let pixel_size = if rgba { 4 } else { 3 };
// yuyv yields 2 3-byte pixels per yuyv chunk
let rgb_buf_size = (data.len() / 4) * (2 * pixel_size);
if dest.len() != rgb_buf_size {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::YUYV,
destination: "RGB888".to_string(),
error: format!("Assertion failure, the destination RGB buffer is of the wrong size! [expected: {rgb_buf_size}, actual: {}]", dest.len()),
});
}
let iter = data.chunks_exact(4);
if rgba {
let mut iter = iter
.flat_map(|yuyv| {
let y1 = yuyv[0] as i32;
let u = yuyv[1] as i32;
let y2 = yuyv[2] as i32;
let v = yuyv[3] as i32;
let pixel1 = yuyv444_to_rgba(y1, u, v);
let pixel2 = yuyv444_to_rgba(y2, u, v);
[pixel1, pixel2]
})
.flatten();
for i in 0..rgb_buf_size {
dest[i] = iter.next().unwrap();
}
} else {
let mut iter = iter
.flat_map(|yuyv| {
let y1 = yuyv[0] as i32;
let u = yuyv[1] as i32;
let y2 = yuyv[2] as i32;
let v = yuyv[3] as i32;
let pixel1 = yuyv444_to_rgb(y1, u, v);
let pixel2 = yuyv444_to_rgb(y2, u, v);
[pixel1, pixel2]
})
.flatten();
for i in 0..rgb_buf_size {
dest[i] = iter.next().unwrap();
}
}
Ok(())
}
// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB
/// Convert `YCbCr` 4:4:4 to a RGB888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB)
#[allow(clippy::many_single_char_names)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[must_use]
#[inline]
pub fn yuyv444_to_rgb(y: i32, u: i32, v: i32) -> [u8; 3] {
let c298 = (y - 16) * 298;
let d = u - 128;
let e = v - 128;
let r = ((c298 + 409 * e + 128) >> 8).clamp(0, 255) as u8;
let g = ((c298 - 100 * d - 208 * e + 128) >> 8).clamp(0, 255) as u8;
let b = ((c298 + 516 * d + 128) >> 8).clamp(0, 255) as u8;
[r, g, b]
}
#[inline]
pub fn yuyv444_to_rgba(y: i32, u: i32, v: i32) -> [u8; 4] {
let [r, g, b] = yuyv444_to_rgb(y, u, v);
[r, g, b, 255]
}