diff --git a/Cargo.lock b/Cargo.lock index e1b3f4e..644149e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -243,11 +243,11 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.7.0", + "bit-vec 0.8.0", ] [[package]] @@ -258,9 +258,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit-vec" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bit_field" @@ -309,9 +309,9 @@ checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -349,7 +349,7 @@ dependencies = [ "log", "nix 0.25.1", "slotmap", - "thiserror 1.0.63", + "thiserror 1.0.69", "vec_map", ] @@ -973,17 +973,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "d3d12" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" -dependencies = [ - "bitflags 2.6.0", - "libloading 0.8.3", - "winapi", -] - [[package]] name = "darling" version = "0.13.4" @@ -1525,6 +1514,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glow" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "glutin_wgl_sys" version = "0.5.0" @@ -1627,22 +1628,21 @@ checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ "log", "presser", - "thiserror 1.0.63", + "thiserror 1.0.69", "winapi", "windows 0.52.0", ] [[package]] name = "gpu-allocator" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" dependencies = [ "log", "presser", - "thiserror 1.0.63", - "winapi", - "windows 0.52.0", + "thiserror 1.0.69", + "windows 0.58.0", ] [[package]] @@ -1721,7 +1721,7 @@ dependencies = [ "com", "libc", "libloading 0.8.3", - "thiserror 1.0.63", + "thiserror 1.0.69", "widestring", "winapi", ] @@ -1763,7 +1763,7 @@ dependencies = [ "byteorder", "color_quant", "num-iter", - "num-rational", + "num-rational 0.3.2", "num-traits", ] @@ -1888,9 +1888,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2288,7 +2288,7 @@ dependencies = [ "rustc-hash", "spirv 0.2.0+1.5.4", "termcolor", - "thiserror 1.0.63", + "thiserror 1.0.69", "unicode-xid", ] @@ -2309,18 +2309,18 @@ dependencies = [ "rustc-hash", "spirv 0.3.0+sdk-1.3.268.0", "termcolor", - "thiserror 1.0.63", + "thiserror 1.0.69", "unicode-xid", ] [[package]] name = "naga" -version = "22.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09eeccb9b50f4f7839b214aa3e08be467159506a986c18e0702170ccf720a453" +checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" dependencies = [ "arrayvec 0.7.4", - "bit-set 0.6.0", + "bit-set 0.8.0", "bitflags 2.6.0", "cfg_aliases", "codespan-reporting", @@ -2330,7 +2330,7 @@ dependencies = [ "rustc-hash", "spirv 0.3.0+sdk-1.3.268.0", "termcolor", - "thiserror 1.0.63", + "thiserror 1.0.69", "unicode-xid", ] @@ -2363,7 +2363,7 @@ dependencies = [ "ndk-sys 0.4.1+23.1.7779620", "num_enum", "raw-window-handle 0.5.2", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -2464,7 +2464,7 @@ dependencies = [ "rgb", "serde", "serde-wasm-bindgen", - "thiserror 1.0.63", + "thiserror 1.0.69", "usb_enumeration", "wasm-bindgen", "wasm-bindgen-futures", @@ -2512,16 +2512,25 @@ version = "0.2.0" dependencies = [ "async-trait", "bytes", - "dcv-color-primitives", + "flume 0.11.0", "futures", "image 0.25.0", - "mozjpeg", + "num-rational 0.4.2", "opencv 0.93.1", "paste", "rgb", "serde", "thiserror 2.0.0", - "wgpu 22.0.0", + "wgpu 23.0.1", +] + +[[package]] +name = "nokhwa-decoder" +version = "0.1.0" +dependencies = [ + "dcv-color-primitives", + "mozjpeg", + "nokhwa-core", "yuvutils-rs", ] @@ -2548,6 +2557,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2580,10 +2599,22 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.18" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -2674,9 +2705,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opencv" @@ -3049,7 +3080,7 @@ checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", "libredox", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] @@ -3436,11 +3467,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.63", + "thiserror-impl 1.0.69", ] [[package]] @@ -3454,9 +3485,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3651,9 +3682,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "usb_enumeration" @@ -3737,19 +3768,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if 1.0.0", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -3762,9 +3794,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3774,9 +3806,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3784,9 +3816,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -3797,9 +3829,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-rs-async-executor" @@ -3885,9 +3917,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -3949,16 +3981,16 @@ dependencies = [ [[package]] name = "wgpu" -version = "22.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87e07e87a179614940ad845397e03201847453a37b43a31a3b54eee2e6e32ce" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" dependencies = [ "arrayvec 0.7.4", "cfg_aliases", "document-features", "js-sys", "log", - "naga 22.0.0", + "naga 23.0.0", "parking_lot", "profiling", "raw-window-handle 0.6.0", @@ -3967,9 +3999,9 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core 22.0.0", - "wgpu-hal 22.0.0", - "wgpu-types 22.0.0", + "wgpu-core 23.0.1", + "wgpu-hal 23.0.1", + "wgpu-types 23.0.0", ] [[package]] @@ -3990,7 +4022,7 @@ dependencies = [ "profiling", "raw-window-handle 0.5.2", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "web-sys", "wgpu-hal 0.14.1", "wgpu-types 0.14.1", @@ -4017,7 +4049,7 @@ dependencies = [ "raw-window-handle 0.6.0", "rustc-hash", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "web-sys", "wgpu-hal 0.20.0", "wgpu-types 0.20.0", @@ -4025,27 +4057,27 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "22.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f191908a21968991463fcf3b42cb6c9648c0fb7fa301b8fc733bc21a9ed9bd" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec 0.7.4", - "bit-vec 0.7.0", + "bit-vec 0.8.0", "bitflags 2.6.0", "cfg_aliases", "document-features", "indexmap 2.2.6", "log", - "naga 22.0.0", + "naga 23.0.0", "once_cell", "parking_lot", "profiling", "raw-window-handle 0.6.0", "rustc-hash", "smallvec", - "thiserror 1.0.63", - "wgpu-hal 22.0.0", - "wgpu-types 22.0.0", + "thiserror 1.0.69", + "wgpu-hal 23.0.1", + "wgpu-types 23.0.0", ] [[package]] @@ -4080,7 +4112,7 @@ dependencies = [ "raw-window-handle 0.5.2", "renderdoc-sys 0.7.1", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types 0.14.1", @@ -4125,7 +4157,7 @@ dependencies = [ "renderdoc-sys 1.1.0", "rustc-hash", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", "wgpu-types 0.20.0", @@ -4134,32 +4166,31 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "22.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" dependencies = [ "android_system_properties", "arrayvec 0.7.4", "ash 0.38.0+1.3.281", - "bit-set 0.6.0", + "bit-set 0.8.0", "bitflags 2.6.0", "block", + "bytemuck", "cfg_aliases", "core-graphics-types", - "d3d12 22.0.0", - "glow 0.13.1", + "glow 0.14.2", "glutin_wgl_sys 0.6.0", "gpu-alloc 0.6.0", - "gpu-allocator 0.26.0", + "gpu-allocator 0.27.0", "gpu-descriptor 0.3.0", - "hassle-rs", "js-sys", "khronos-egl 6.0.0", "libc", "libloading 0.8.3", "log", "metal 0.29.0", - "naga 22.0.0", + "naga 23.0.0", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", @@ -4170,11 +4201,12 @@ dependencies = [ "renderdoc-sys 1.1.0", "rustc-hash", "smallvec", - "thiserror 1.0.63", + "thiserror 1.0.69", "wasm-bindgen", "web-sys", - "wgpu-types 22.0.0", - "winapi", + "wgpu-types 23.0.0", + "windows 0.58.0", + "windows-core 0.58.0", ] [[package]] @@ -4199,9 +4231,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "22.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ "bitflags 2.6.0", "js-sys", @@ -4715,9 +4747,12 @@ checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" [[package]] name = "yuvutils-rs" -version = "0.4.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b0c026d402c14683f21eae52b33dd76ba47c548273a7eb526eed5ce2f46b65" +checksum = "b30a62ce6c5fbf13dbf8d92e7cd805d74574a7f84d0e81462ca3cb8192be5de2" +dependencies = [ + "num-traits", +] [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index b7f8ab3..07c2f40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/l1npengtul/nokhwa" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] -members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-bindings-linux", "nokhwa-core", "examples/*"] +members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-bindings-linux", "nokhwa-core", "examples/*", "nokhwa-decoder"] exclude = ["examples/jscam"] [lib] diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 8f6c968..6004eed 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -34,14 +34,14 @@ use nokhwa::{ frame_formats, yuyv422_predicted_size, CameraFormat, CameraIndex, FrameFormat, RequestedFormat, RequestedFormatType, Resolution, }, - Buffer, CallbackCamera, Camera, + FrameBuffer, CallbackCamera, Camera, }; use std::str::FromStr; use std::sync::Arc; use std::time::Duration; struct CaptureState { - receiver: Arc>, + receiver: Arc>, buffer: Vec, format: CameraFormat, } diff --git a/examples/decoder_test/src/main.rs b/examples/decoder_test/src/main.rs index c269b33..c9d3c87 100644 --- a/examples/decoder_test/src/main.rs +++ b/examples/decoder_test/src/main.rs @@ -1,4 +1,4 @@ -use nokhwa_core::buffer::Buffer; +use nokhwa_core::frame_buffer::FrameBuffer; use nokhwa_core::pixel_format::RgbFormat; use nokhwa_core::types::{FrameFormat, Resolution}; use std::fs::File; @@ -11,7 +11,7 @@ fn main() { .read_to_end(&mut nv12) .unwrap(); - let buffer = Buffer::new(Resolution::new(1920, 1080), &nv12, FrameFormat::NV12); + let buffer = FrameBuffer::new(Resolution::new(1920, 1080), &nv12, FrameFormat::NV12); buffer .decode_image::() .unwrap() diff --git a/flake.nix b/flake.nix index 0b3e18c..10f4496 100644 --- a/flake.nix +++ b/flake.nix @@ -40,7 +40,8 @@ rustPlatform.bindgenHook llvmPackages.libclang.lib llvmPackages.clang - v4l-utils.override { withUtils = true; } + v4l-utils + libv4l ]; LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; shellHook = '' diff --git a/nokhwa-bindings-linux/src/v4l2.rs b/nokhwa-bindings-linux/src/v4l2.rs index 9cf09fc..98ab73f 100644 --- a/nokhwa-bindings-linux/src/v4l2.rs +++ b/nokhwa-bindings-linux/src/v4l2.rs @@ -8,8 +8,8 @@ use v4l::frameinterval::FrameIntervalEnum; use v4l::prelude::MmapStream; use v4l::video::{Capture as V4lCapture, Output}; use v4l::video::output::Parameters; -use nokhwa_core::buffer::Buffer; -use nokhwa_core::capture::{Capture, Open, Setting, Stream}; +use nokhwa_core::frame_buffer::FrameBuffer; +use nokhwa_core::camera::{Camera, Open, Setting, Capture}; use nokhwa_core::properties::{CameraProperties, CameraPropertyFlag, CameraPropertyId, CameraPropertyValue}; use nokhwa_core::{define_back_and_fourth_control, define_back_and_fourth_frame_format}; use nokhwa_core::error::{NokhwaError, NokhwaResult}; diff --git a/nokhwa-core/Cargo.toml b/nokhwa-core/Cargo.toml index 91edca6..c96970f 100644 --- a/nokhwa-core/Cargo.toml +++ b/nokhwa-core/Cargo.toml @@ -14,10 +14,9 @@ repository = "https://github.com/l1npengtul/nokhwa" default = [] serialize = ["serde"] wgpu-types = ["wgpu"] -opencv-mat = ["opencv"] +opencv-mat = ["opencv", "opencv/clang-runtime"] docs-features = ["serialize", "wgpu-types"] -conversions = ["dcv-color-primitives", "yuvutils-rs", "mozjpeg"] -async = ["async-trait"] +async = ["async-trait", "flume/async"] test-fail-warnings = [] @@ -25,6 +24,12 @@ test-fail-warnings = [] thiserror = "2.0" bytes = "1.3" paste = "1.0" +flume = "0.11" + +[dependencies.num-rational] +version = "0.4" +default-features = false +features = ["serde", "std"] [dependencies.image] version = "0.25" @@ -36,7 +41,7 @@ features = ["derive"] optional = true [dependencies.wgpu] -version = "22" +version = "23" optional = true [dependencies.opencv] @@ -44,22 +49,10 @@ version = "0.93" default-features = false optional = true -[dependencies.mozjpeg] -version = "0.10" -optional = true - [dependencies.async-trait] version = "0.1" optional = true -[dependencies.dcv-color-primitives] -version = "0.6" -optional = true - -[dependencies.yuvutils-rs] -version = "0.4" -optional = true - [dependencies.futures] version = "0.3" optional = true diff --git a/nokhwa-core/src/camera.rs b/nokhwa-core/src/camera.rs new file mode 100644 index 0000000..f1f80c6 --- /dev/null +++ b/nokhwa-core/src/camera.rs @@ -0,0 +1,132 @@ +use crate::error::{NokhwaError, NokhwaResult}; +use crate::frame_buffer::FrameBuffer; +use crate::frame_format::FrameFormat; +use crate::properties::{CameraProperties, CameraPropertyId, CameraPropertyValue}; +use crate::types::{CameraFormat, CameraIndex, FrameRate, Resolution}; +use std::collections::HashMap; +use crate::stream::Stream; + +pub trait Open { + fn open(index: CameraIndex) -> NokhwaResult where Self: Sized; +} + +#[cfg(feature = "async")] +pub trait AsyncOpen: Sized { + async fn open_async(index: CameraIndex) -> NokhwaResult; +} + +macro_rules! def_camera_props { + ( $($property:ident, )* ) => { + paste::paste! { + $( + fn [<$property:snake>] (&self) -> Option<&crate::properties::CameraPropertyDescriptor> { + self.properties().[<$property:snake>]() + } + + fn [] (&mut self, value: crate::properties::CameraPropertyValue) -> Result<(), crate::error::NokhwaError> { + self.set_property(&crate::properties::CameraPropertyId::$property, value) + } + )* + } + }; +} + +// macro_rules! def_camera_props_async { +// ( $($property:ident, )* ) => { +// paste::paste! { +// $( +// async fn [] (&mut self, value: CameraPropertyValue) -> Result<(), NokhwaError> { +// self.[](value) +// } +// )* +// } +// }; +// } + +pub trait Setting { + fn enumerate_formats(&self) -> Result, NokhwaError>; + + fn enumerate_resolution_and_frame_rates( + &self, + frame_format: FrameFormat, + ) -> Result>, NokhwaError>; + + fn set_format(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>; + + fn properties(&self) -> &CameraProperties; + + fn set_property( + &mut self, + property: &CameraPropertyId, + value: CameraPropertyValue, + ) -> Result<(), NokhwaError>; + + def_camera_props!( + Brightness, + Contrast, + Hue, + Saturation, + Sharpness, + Gamma, + WhiteBalance, + BacklightCompensation, + Pan, + Tilt, + Zoom, + Exposure, + Iris, + Focus, + Facing, + ); +} + +// #[cfg(feature = "async")] +// pub trait AsyncSetting { +// async fn set_format_async(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>; +// +// async fn set_property_async( +// &mut self, +// property: &CameraPropertyId, +// value: CameraPropertyValue, +// ) -> Result<(), NokhwaError>; +// +// def_camera_props_async!( +// Brightness, +// Contrast, +// Hue, +// Saturation, +// Sharpness, +// Gamma, +// WhiteBalance, +// BacklightCompensation, +// Pan, +// Tilt, +// Zoom, +// Exposure, +// Iris, +// Focus, +// Facing, +// ); +// } + +pub trait Capture { + fn open_stream(&mut self) -> Result; + + fn close_stream(&mut self) -> Result<(), NokhwaError>; +} + +#[cfg(feature = "async")] +pub trait AsyncStream { + async fn open_stream(&mut self) -> Result<(), NokhwaError>; + + async fn await_frame(&mut self) -> Result; + + async fn close_stream(&mut self) -> Result<(), NokhwaError>; +} + +pub trait CameraVtable: Setting + Capture {} + +pub trait Camera: Open + CameraVtable {} + +#[cfg(feature = "async")] +pub trait AsyncCapture: Camera + AsyncOpen + AsyncStream {} diff --git a/nokhwa-core/src/capture.rs b/nokhwa-core/src/capture.rs deleted file mode 100644 index ed28eeb..0000000 --- a/nokhwa-core/src/capture.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::collections::HashMap; -use crate::buffer::Buffer; -use crate::properties::{CameraProperties, CameraPropertyId, CameraPropertyValue}; -use crate::error::{NokhwaError, NokhwaResult}; -use crate::frame_format::FrameFormat; -use crate::stream::CaptureStream; -use crate::types::{CameraFormat, CameraIndex, FrameRate, Resolution}; - -pub trait Open { - fn open(index: CameraIndex) -> NokhwaResult; -} - -#[cfg(feature = "async")] -pub trait AsyncOpen { - async fn open_async(index: CameraIndex) -> NokhwaResult; -} - -macro_rules! def_camera_props { - ( $($property:ident, )* ) => { - paste::paste! { - $( - fn [<$property:snake>] (&self) -> Option<&CameraPropertyDescriptor> { - self.properties().[<$property:snake>] - } - - fn [] (&mut self, value: CameraPropertyValue) -> Result<(), NokhwaError> { - self.properties().[](value) - } - )* - } - }; -} - -macro_rules! def_camera_props_async { - ( $($property:ident, )* ) => { - paste::paste! { - $( - async fn [] (&mut self, value: CameraPropertyValue) -> Result<(), NokhwaError> { - self.properties().[](value) - } - )* - } - }; -} - -pub trait Setting { - fn enumerate_formats(&self) -> Result, NokhwaError>; - - fn enumerate_resolution_and_frame_rates(&self, frame_format: FrameFormat) -> Result>, NokhwaError>; - - fn set_format(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>; - - fn properties(&self) -> &CameraProperties; - - fn set_property(&mut self, property: &CameraPropertyId, value: CameraPropertyValue) -> Result<(), NokhwaError>; - - def_camera_props!( - Brightness, - Contrast, - Hue, - Saturation, - Sharpness, - Gamma, - WhiteBalance, - BacklightCompensation, - Gain, - Pan, - Tilt, - Zoom, - Exposure, - Iris, - Focus, - Facing, - ); -} - -#[cfg(feature = "async")] -pub trait AsyncSetting { - async fn set_format_async(&self, camera_format: CameraFormat) -> Result<(), NokhwaError>; - - async fn set_property_async(&mut self, property: &CameraPropertyId, value: CameraPropertyValue) -> Result<(), NokhwaError>; - - def_camera_props_async!( - Brightness, - Contrast, - Hue, - Saturation, - Sharpness, - Gamma, - WhiteBalance, - BacklightCompensation, - Gain, - Pan, - Tilt, - Zoom, - Exposure, - Iris, - Focus, - Facing, - ); -} - -pub trait Stream { - fn open_stream(&mut self) -> Result; - - fn close_stream(&mut self) -> Result<(), NokhwaError>; -} - -#[cfg(feature = "async")] -pub trait AsyncStream { - async fn open_stream(&mut self) -> Result<(), NokhwaError>; - - async fn await_frame(&mut self) -> Result; - - async fn close_stream(&mut self) -> Result<(), NokhwaError>;} - -pub trait Capture: Open + Setting + Stream {} - -#[cfg(feature = "async")] -pub trait AsyncCapture: Capture + AsyncOpen + AsyncSetting + AsyncStream {} - - diff --git a/nokhwa-core/src/decoder.rs b/nokhwa-core/src/decoder.rs new file mode 100644 index 0000000..2795e58 --- /dev/null +++ b/nokhwa-core/src/decoder.rs @@ -0,0 +1,104 @@ +use crate::{error::NokhwaError, frame_buffer::FrameBuffer, frame_format::FrameFormat}; +use image::{ImageBuffer, Pixel}; +use std::{ + error::Error, + fmt::{Debug, Display}, + ops::{ControlFlow, Deref}, +}; + +/// Trait to define a struct that can decode a [`FrameBuffer`] +pub trait Decoder { + /// Formats that the decoder can decode. + const ALLOWED_FORMATS: &'static [FrameFormat]; + /// Output pixel type (e.g. [`Rgb`](image::Rgb)) + type OutputPixels: Pixel; + + /// Container type for the decoder. Will be used for ImageBuffer + type PixelContainer: Deref::OutputPixels as Pixel>::Subpixel]>; + + fn check_format(buffer: &FrameBuffer) -> ControlFlow { + if !Self::ALLOWED_FORMATS.contains(&buffer.source_frame_format()) { + return ControlFlow::Break(NokhwaError::ConversionError("unsupported".to_string())); + } + + ControlFlow::Continue(()) + } + + /// Decode function. + fn decode( + &mut self, + buffer: &FrameBuffer, + ) -> Result, NokhwaError>; + + /// Decode to user-provided Buffer + /// + /// Incase that the buffer is not large enough this should error. + fn decode_buffer( + &mut self, + buffer: &FrameBuffer, + output: &mut [<::OutputPixels as Pixel>::Subpixel], + ) -> Result<(), NokhwaError>; + + /// Decoder Predicted Size + fn predicted_size_of_frame(buffer: &FrameBuffer) -> Option { + if !Self::ALLOWED_FORMATS.contains(&buffer.source_frame_format()) { + return None; + } + let res = buffer.resolution(); + Some( + res.x() as usize + * res.y() as usize + * size_of::<<::OutputPixels as Pixel>::Subpixel>() + * <::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: &FrameBuffer, + ) -> Result, NokhwaError>; + + fn decode_static_to_buffer( + buffer: &FrameBuffer, + output: &mut [<::OutputPixels as Pixel>::Subpixel], + ) -> Result<(), NokhwaError>; +} + +#[cfg(feature = "async")] +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AsyncDecoder: Decoder { + /// Asynchronous decoder + async fn decode_async( + &mut self, + buffer: &FrameBuffer, + ) -> Result, NokhwaError>; + + /// Asynchronous decoder to user buffer. + async fn decode_buffer( + &mut self, + buffer: &FrameBuffer, + output: &mut [<::OutputPixels as Pixel>::Subpixel], + ) -> Result<(), NokhwaError>; +} + +#[cfg(feature = "async")] +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AsyncStaticDecoder: Decoder { + /// Asynchronous decoder + async fn decode_static_async( + buffer: &FrameBuffer, + ) -> Result, NokhwaError>; + + /// Asynchronous decoder to user buffer. + async fn decode_static_buffer( + &mut self, + buffer: &FrameBuffer, + output: &mut [<::OutputPixels as Pixel>::Subpixel], + ) -> Result<(), NokhwaError>; +} + +// #[cfg(feature = "decoders")] diff --git a/nokhwa-core/src/decoders/general.rs b/nokhwa-core/src/decoders/general.rs deleted file mode 100644 index 8efcf48..0000000 --- a/nokhwa-core/src/decoders/general.rs +++ /dev/null @@ -1,60 +0,0 @@ -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 where D: PixelWithColorType; - -impl Decoder for GeneralPurposeDecoder where D: PixelWithColorType { - const ALLOWED_FORMATS: &'static [FrameFormat] = &[ - FrameFormat::MJpeg, FrameFormat::Luma8, FrameFormat::Luma16, FrameFormat::Rgb332, FrameFormat::RgbA8888, - FrameFormat::Nv12, FrameFormat::Nv21, FrameFormat::Uyvy422, FrameFormat::Yuy2_422, FrameFormat::Yv12, - FrameFormat::Ayuv444, FrameFormat::I420, FrameFormat::I422, FrameFormat::I444 - ]; - - type OutputPixels = D; - type PixelContainer = Vec; - type Error = NokhwaError; - - fn decode(&mut self, buffer: Buffer) -> Result, Self::Error> { - todo!() - } - - fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<::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::Uyvy422 => PixelFormat::I422, - FrameFormat::Ayuv444 => PixelFormat::I444, - FrameFormat::Nv12 => PixelFormat::Nv12, - FrameFormat::Nv21 => PixelFormat::Nv12, - FrameFormat::Yv12 => PixelFormat::I420, - FrameFormat::I420 => PixelFormat::I420, - // already decoded - FrameFormat::Rgb332 => PixelFormat::Rgb, - FrameFormat::RgbA8888 => { - PixelFormat::Rgb - } - _ => return Err(()), - }; - - dcv_color_primitives::convert_image(buffer.resolution().width(), buffer.resolution().height(), ) - - } -} diff --git a/nokhwa-core/src/decoders/mod.rs b/nokhwa-core/src/decoders/mod.rs deleted file mode 100644 index da9d2d7..0000000 --- a/nokhwa-core/src/decoders/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ - -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`](image::Rgb)) - type OutputPixels: Pixel; - - type PixelContainer: Deref::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, 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 [<::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>; - - /// Decoder Predicted Size - fn predicted_size_of_frame(buffer: &Buffer) -> Option { - 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::<<::OutputPixels as Pixel>::Subpixel>() * <::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, Self::Error>; - - fn decode_static_to_buffer(&mut self, buffer: &Buffer, output: &mut [<::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, Self::Error>; - - /// Asynchronous decoder to user buffer. - async fn decode_buffer(&mut self, buffer: &Buffer, output: &mut [<::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, Self::Error>; - - /// Asynchronous decoder to user buffer. - async fn decode_static_buffer(&mut self, buffer: &Buffer, output: &mut [<::OutputPixels as Pixel>::Subpixel]) -> Result<(), Self::Error>; -} - -#[cfg(feature = "conversions")] -pub mod general; diff --git a/nokhwa-core/src/error.rs b/nokhwa-core/src/error.rs index 0ed1392..c882b18 100644 --- a/nokhwa-core/src/error.rs +++ b/nokhwa-core/src/error.rs @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - use crate::{frame_format::FrameFormat, types::ApiBackend}; +use std::fmt::{Debug}; use thiserror::Error; pub type NokhwaResult = Result; diff --git a/nokhwa-core/src/format_request.rs b/nokhwa-core/src/format_request.rs index 116527e..c1c0581 100644 --- a/nokhwa-core/src/format_request.rs +++ b/nokhwa-core/src/format_request.rs @@ -1,13 +1,10 @@ -use std::{ - cmp::Ordering, - collections::VecDeque -}; +use crate::utils::Distance; use crate::{ frame_format::FrameFormat, - traits::Distance, - types::{CameraFormat, FrameRate, Resolution} + ranges::Range, + types::{CameraFormat, FrameRate, Resolution}, }; -use crate::ranges::Range; +use std::cmp::Ordering; #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] enum ClosestType { @@ -25,7 +22,12 @@ pub enum CustomFormatRequestType { Exact, } +/// A helper for choosing a [`CameraFormat`]. +/// The use of this is completely optional - for a simpler way try [`crate::camera::Camera::enumerate_formats`]. +/// +/// The `frame_format` field filters out the [`CameraFormat`]s by [`FrameFormat`]. pub enum FormatRequest { + /// Pick the closest [`CameraFormat`] to the one requested Closest { resolution: Option>, frame_rate: Option>, @@ -47,51 +49,88 @@ pub enum FormatRequest { } impl FormatRequest { + pub fn sort_formats(&self, list_of_formats: &[CameraFormat]) -> Vec { + if list_of_formats.is_empty() { + return vec![]; + } + + match self { + FormatRequest::Closest { + resolution, + frame_rate, + frame_format, + } => { + let resolution_point = resolution.map(|x| x.preferred())?; + + 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: Vec<(f32, CameraFormat)> = 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::>(); + distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal)); + distances.into_iter().map(|x| x.1).collect() + } + 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::>(); + formats.sort(); + formats.into_iter().copied().collect() + } + 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::>(); + formats.sort(); + formats.into_iter().copied().collect() + } + 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::>(); + formats.sort(); + formats.into_iter().copied().collect() + } + } + } + + /// #[must_use] pub fn resolve(&self, list_of_formats: &[CameraFormat]) -> Option { if list_of_formats.is_empty() { return None; } - match self { - FormatRequest::Closest { resolution, frame_rate, frame_format } => { - let resolution_point = resolution.map(|x| x.preferred())?; - - 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 = 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::>(); - 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() - } - 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::>(); - formats.sort(); - 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::>(); - formats.sort(); - 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::>(); - formats.sort(); - formats.first().copied().copied() - } - } + Some(self.sort_formats(list_of_formats).remove(0)) } } diff --git a/nokhwa-core/src/buffer.rs b/nokhwa-core/src/frame_buffer.rs similarity index 97% rename from nokhwa-core/src/buffer.rs rename to nokhwa-core/src/frame_buffer.rs index e0418ca..08bedb8 100644 --- a/nokhwa-core/src/buffer.rs +++ b/nokhwa-core/src/frame_buffer.rs @@ -14,22 +14,22 @@ * limitations under the License. */ +use crate::frame_format::FrameFormat; 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. /// /// Note that decoding on the main thread **will** decrease your performance and lead to dropped frames. #[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Eq)] -pub struct Buffer { +pub struct FrameBuffer { resolution: Resolution, buffer: Bytes, source_frame_format: FrameFormat, } -impl Buffer { +impl FrameBuffer { /// Creates a new buffer with a [`&[u8]`]. #[must_use] #[inline] @@ -53,7 +53,7 @@ impl Buffer { &self.buffer } - /// Get an owned version of this buffer. Note: This is the equivalent + /// Get an owned version of this buffer. Note: This is the equivalent #[must_use] pub fn buffer_bytes(&self) -> Bytes { self.buffer.clone() diff --git a/nokhwa-core/src/frame_format.rs b/nokhwa-core/src/frame_format.rs index b6e74cd..1776069 100644 --- a/nokhwa-core/src/frame_format.rs +++ b/nokhwa-core/src/frame_format.rs @@ -16,8 +16,6 @@ use std::fmt::{Display, Formatter}; -use crate::types::ApiBackend; - /// Describes a frame format (i.e. how the bytes themselves are encoded). Often called `FourCC`. #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -40,7 +38,6 @@ pub enum FrameFormat { // YCbCr Formats // 8 bit per pixel, 4:4:4 - Ayuv444, // -> 4:2:2 @@ -49,14 +46,12 @@ pub enum FrameFormat { Yvyu422, Yv12, - // 4:2:0 Nv12, Nv21, I420, // 16:1:1 - Yvu9, // Grayscale Formats @@ -70,7 +65,7 @@ pub enum FrameFormat { Rgb332, Rgb555, Rgb565, - + Rgb888, RgbA8888, @@ -136,7 +131,7 @@ impl FrameFormat { pub const LUMA: &'static [FrameFormat] = &[FrameFormat::Luma8, FrameFormat::Luma16]; pub const RGB: &'static [FrameFormat] = &[FrameFormat::Rgb332, FrameFormat::RgbA8888]; - + pub const COLOR_FORMATS: &'static [FrameFormat] = &[ FrameFormat::H265, FrameFormat::H264, @@ -158,7 +153,7 @@ impl FrameFormat { FrameFormat::Rgb332, FrameFormat::RgbA8888, ]; - + pub const GRAYSCALE: &'static [FrameFormat] = &[FrameFormat::Luma8, FrameFormat::Luma16]; } @@ -172,7 +167,7 @@ impl Display for FrameFormat { macro_rules! define_back_and_fourth_frame_format { ($fourcc_type:ty, { $( $frame_format:expr => $value:literal, )* }, $func_u8_8_to_fcc:expr, $func_fcc_to_u8_8:expr, $value_to_fcc_type:expr) => { pub struct FrameFormatIntermediate(pub $fourcc_type); - + impl FrameFormatIntermediate { pub fn from_frame_format(frame_format: FrameFormat) -> Option { match frame_format { @@ -183,7 +178,7 @@ macro_rules! define_back_and_fourth_frame_format { _ => None, } } - + pub fn into_frame_format(fourcc: $fourcc_type) -> FrameFormat { match fourcc.0 { $( diff --git a/nokhwa-core/src/lib.rs b/nokhwa-core/src/lib.rs index f187a44..2490271 100644 --- a/nokhwa-core/src/lib.rs +++ b/nokhwa-core/src/lib.rs @@ -22,18 +22,16 @@ */ //! Core type definitions for `nokhwa` -pub mod buffer; +pub mod camera; +pub mod decoder; pub mod error; pub mod format_request; +pub mod frame_buffer; pub mod frame_format; +pub mod properties; +pub mod query; +pub mod ranges; pub mod traits; pub mod types; -pub mod decoders; pub mod utils; -pub mod ranges; -pub mod properties; -pub mod capture; -pub mod query; - -#[cfg(feature = "async")] pub mod stream; diff --git a/nokhwa-core/src/properties.rs b/nokhwa-core/src/properties.rs index b4b4ff0..f36bc0e 100644 --- a/nokhwa-core/src/properties.rs +++ b/nokhwa-core/src/properties.rs @@ -1,11 +1,11 @@ -use serde::{Deserialize, Serialize}; -use std::{ - fmt::{Display, Formatter}, - collections::{HashMap, HashSet} -}; -use std::cmp::Ordering; use crate::error::NokhwaError; -use crate::ranges::{ArrayRange, IndicatedRange, KeyValue, Options, RangeValidationFailure, Simple, ValidatableRange}; +use crate::ranges::{ArrayRange, KeyValue, Options, Range, RangeValidationFailure, Simple, ValidatableRange}; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::{ + collections::{HashMap, HashSet}, + fmt::{Display, Formatter}, +}; #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ControlValidationFailure; @@ -34,12 +34,18 @@ pub enum CameraPropertyId { Iris, Focus, Facing, - Custom(String) + Custom(String), +} + +impl Display for CameraPropertyId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } } // TODO: Replace Controls API with Properties. (this one) /// Properties of a Camera. -/// +/// /// If the property is not supported, it is `None`. /// Custom or platform-specific properties go into `other` pub struct CameraProperties { @@ -54,9 +60,9 @@ macro_rules! def_camera_props { pub fn [<$property:snake>] (&self) -> Option<&CameraPropertyDescriptor> { self.props.get(&CameraPropertyId::$property) } - + pub fn [] (&mut self, value: CameraPropertyValue) -> Result<(), NokhwaError> { - self.props.set_property(&CameraPropertyId::$property, value) + self.set_property(&CameraPropertyId::$property, value) } )* } @@ -80,27 +86,29 @@ def_camera_props!( Exposure, Iris, Focus, - Facing, + Facing, ); -impl CameraProperties { +impl CameraProperties { pub fn property(&self, property: &CameraPropertyId) -> Option<&CameraPropertyDescriptor> { self.props.get(property) } - pub fn set_property(&mut self, property: &CameraPropertyId, value: CameraPropertyValue) -> Result<(), NokhwaError> { + pub fn set_property( + &mut self, + property: &CameraPropertyId, + value: CameraPropertyValue, + ) -> Result<(), NokhwaError> { match self.props.get_mut(property) { - Some(prop) => { + Some(prop) => { prop.set_value(value)?; Ok(()) } - None => { - Err(NokhwaError::SetPropertyError { - property: property.to_string(), - value: value.to_string(), - error: String::from("Is null."), - }) - } + None => Err(NokhwaError::SetPropertyError { + property: property.to_string(), + value: value.to_string(), + error: String::from("Is null."), + }), } } } @@ -116,10 +124,20 @@ pub struct CameraPropertyDescriptor { } impl CameraPropertyDescriptor { - pub fn new(flags: &[CameraPropertyFlag], mode: CameraPropertyMode, range: CameraPropertyRange, value: CameraPropertyValue, value_type: CameraPropertyValueType) -> Result { - - if flags.contains(&CameraPropertyFlag::ReadOnly) && flags.contains(&CameraPropertyFlag::WriteOnly) { - return Err(NokhwaError::StructureError { structure: "CameraPropertyDescriptor".to_string(), error: "conflicting flags".to_string() }) + pub fn new( + flags: &[CameraPropertyFlag], + mode: CameraPropertyMode, + range: CameraPropertyRange, + value: CameraPropertyValue, + value_type: CameraPropertyValueType, + ) -> Result { + if flags.contains(&CameraPropertyFlag::ReadOnly) + && flags.contains(&CameraPropertyFlag::WriteOnly) + { + return Err(NokhwaError::StructureError { + structure: "CameraPropertyDescriptor".to_string(), + error: "conflicting flags".to_string(), + }); } Ok(CameraPropertyDescriptor { @@ -130,18 +148,21 @@ impl CameraPropertyDescriptor { value_type, }) } - + pub fn is_read_only(&self) -> bool { self.flags.contains(&CameraPropertyFlag::ReadOnly) } - + pub fn is_write_only(&self) -> bool { self.flags.contains(&CameraPropertyFlag::WriteOnly) } - + pub fn is_disabled(&self) -> Result<(), NokhwaError> { if self.flags.contains(&CameraPropertyFlag::Disabled) { - return Err(NokhwaError::StructureError { structure: "CameraPropertyDescriptor".to_string(), error: "Disabled".to_string() }) + return Err(NokhwaError::StructureError { + structure: "CameraPropertyDescriptor".to_string(), + error: "Disabled".to_string(), + }); } Ok(()) } @@ -168,11 +189,13 @@ impl CameraPropertyDescriptor { } pub fn set_value(&mut self, value: CameraPropertyValue) -> Result<(), NokhwaError> { - self.range.check_value(&value).map_err(|_| NokhwaError::SetPropertyError { - property: "CameraPropertyValue".to_string(), - value: value.to_string(), - error: "Bad Type".to_string(), - })?; + self.range + .check_value(&value) + .map_err(|_| NokhwaError::SetPropertyError { + property: "CameraPropertyValue".to_string(), + value: value.to_string(), + error: "Bad Type".to_string(), + })?; self.value = value; Ok(()) } @@ -239,18 +262,27 @@ impl Display for CameraPropertyFlag { pub enum CameraPropertyRange { Null, Boolean(Simple), - Integer(IndicatedRange), - LongInteger(IndicatedRange), - Float(IndicatedRange), - Double(IndicatedRange), + Integer(Range), + LongInteger(Range), + Float(Range), + Double(Range), String(Simple), Array(ArrayRange>), Enumeration(Options), Binary(Simple>), - Pair(IndicatedRange, IndicatedRange), - Triple(IndicatedRange, IndicatedRange, IndicatedRange), - Quadruple(IndicatedRange, IndicatedRange, IndicatedRange, IndicatedRange), - KeyValuePair(KeyValue) + Pair(Range, Range), + Triple( + Range, + Range, + Range, + ), + Quadruple( + Range, + Range, + Range, + Range, + ), + KeyValuePair(KeyValue), } impl CameraPropertyRange { @@ -258,7 +290,7 @@ impl CameraPropertyRange { match self { CameraPropertyRange::Null => { if let CameraPropertyValue::Null = value { - return Ok(()) + return Ok(()); } } CameraPropertyRange::Boolean(chk_b) => { @@ -331,7 +363,7 @@ impl CameraPropertyRange { if let CameraPropertyValue::KeyValue(st, va) = value { if let Some(vk) = kv.by_key(st) { if vk.is_same_type(va) { - return Ok(()) + return Ok(()); } } } @@ -361,7 +393,7 @@ pub enum CameraPropertyValue { Pair(f32, f32), Triple(f32, f32, f32), Quadruple(f32, f32, f32, f32), - KeyValue(String, Box) + KeyValue(String, Box), } impl CameraPropertyValue { @@ -441,22 +473,22 @@ impl PartialEq for CameraPropertyValue { } CameraPropertyValue::Pair(a, b) => { if let CameraPropertyValue::Pair(oa, ob) = other { - return (a == oa) && (b == ob) + return (a == oa) && (b == ob); } } CameraPropertyValue::Triple(x, y, z) => { if let CameraPropertyValue::Triple(ox, oy, oz) = other { - return (x == ox) && (y == oy) && (z == oz) + return (x == ox) && (y == oy) && (z == oz); } } CameraPropertyValue::Quadruple(x, y, z, w) => { if let CameraPropertyValue::Quadruple(ox, oy, oz, ow) = other { - return (x == ox) && (y == oy) && (z == oz) && (w == ow) + return (x == ox) && (y == oy) && (z == oz) && (w == ow); } } CameraPropertyValue::KeyValue(k, v) => { - if let CameraPropertyValue::KeyValue(ok, ov ) = other { - return (k == ok) && (v == ov) + if let CameraPropertyValue::KeyValue(ok, ov) = other { + return (k == ok) && (v == ov); } } _ => {} @@ -468,145 +500,109 @@ impl PartialEq for CameraPropertyValue { impl PartialOrd for CameraPropertyValue { fn partial_cmp(&self, other: &Self) -> Option { match self { - CameraPropertyValue::Null => { - match other { - CameraPropertyValue::Null => Some(Ordering::Greater), - _ => Some(Ordering::Less) + CameraPropertyValue::Null => match other { + CameraPropertyValue::Null => Some(Ordering::Greater), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::Boolean(b) => match other { + CameraPropertyValue::Null => Some(Ordering::Greater), + CameraPropertyValue::Boolean(o) => { + if o == b { + Some(Ordering::Equal) + } else if o { + Some(Ordering::Less) + } else { + Some(Ordering::Greater) + } } - } - CameraPropertyValue::Boolean(b) => { - match other { - CameraPropertyValue::Null => Some(Ordering::Greater), - CameraPropertyValue::Boolean(o) => { - if o == b { - Some(Ordering::Equal) - } else if o { - Some(Ordering::Less) - } else { - Some(Ordering::Greater) - } - } - _ => Some(Ordering::Less) + _ => Some(Ordering::Less), + }, + CameraPropertyValue::Integer(int) => match other { + CameraPropertyValue::Null | CameraPropertyValue::Boolean(_) => { + Some(Ordering::Greater) } - } - CameraPropertyValue::Integer(int) => { - match other { - CameraPropertyValue::Null | CameraPropertyValue::Boolean(_) => Some(Ordering::Greater), - CameraPropertyValue::Integer(oth) => { - Some(int.cmp(oth)) - } - CameraPropertyValue::LongInteger(li) => { - let long = match i64::try_from(li) { - Ok(v) => v, - Err(_) => return None, - }; - Some(int.cmp(&long)) - } - _ => Some(Ordering::Less), + CameraPropertyValue::Integer(oth) => Some(int.cmp(oth)), + CameraPropertyValue::LongInteger(li) => { + let long = match i64::try_from(li) { + Ok(v) => v, + Err(_) => return None, + }; + Some(int.cmp(&long)) } - } - CameraPropertyValue::LongInteger(long) => { - match other { - CameraPropertyValue::Null | CameraPropertyValue::Boolean(_) => Some(Ordering::Greater), - CameraPropertyValue::Integer(oth) => { - Some(long.cmp(&(i128::from(oth)))) - } - CameraPropertyValue::LongInteger(o) => { - Some(long.cmp(o)) - } - _ => Some(Ordering::Less), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::LongInteger(long) => match other { + CameraPropertyValue::Null | CameraPropertyValue::Boolean(_) => { + Some(Ordering::Greater) } - } - CameraPropertyValue::Float(fl) => { - match other { - CameraPropertyValue::Null | - CameraPropertyValue::Boolean(_) | - CameraPropertyValue::Integer(_) | - CameraPropertyValue::LongInteger(_) => Some(Ordering::Greater), - CameraPropertyValue::Float(f) => { - fl.partial_cmp(f) - } - CameraPropertyValue::Double(d) => { - f64::from(fl).partial_cmp(d) - } - _ => Some(Ordering::Less), - } - } - CameraPropertyValue::Double(d) => { - match other { - CameraPropertyValue::Null | - CameraPropertyValue::Boolean(_) | - CameraPropertyValue::Integer(_) | - CameraPropertyValue::LongInteger(_) => Some(Ordering::Greater), - CameraPropertyValue::Float(f) => { - d.partial_cmp(&(f64::from(f))) - } - CameraPropertyValue::Double(o) => { - d.partial_cmp(o) - } - _ => Some(Ordering::Less), - } - } - CameraPropertyValue::String(s) => { - match other { - CameraPropertyValue::Null | - CameraPropertyValue::Boolean(_) | - CameraPropertyValue::Integer(_) | - CameraPropertyValue::LongInteger(_) | - CameraPropertyValue::Float(_) | - CameraPropertyValue::Double(_) => Some(Ordering::Greater), - CameraPropertyValue::String(os) => { - s.partial_cmp(os) - } - _ => Some(Ordering::Less), - } - } - CameraPropertyValue::Array(a) => { - match other { - CameraPropertyValue::Null | - CameraPropertyValue::Boolean(_) | - CameraPropertyValue::Integer(_) | - CameraPropertyValue::LongInteger(_) | - CameraPropertyValue::Float(_) | - CameraPropertyValue::Double(_) | - CameraPropertyValue::String(_) => Some(Ordering::Greater), - CameraPropertyValue::Array(oa) => { - a.partial_cmp(oa) - } - _ => Some(Ordering::Less), - } - } - CameraPropertyValue::EnumValue(_) => { - match other { - CameraPropertyValue::Null | - CameraPropertyValue::Boolean(_) | - CameraPropertyValue::Integer(_) | - CameraPropertyValue::LongInteger(_) | - CameraPropertyValue::Float(_) | - CameraPropertyValue::Double(_) | - CameraPropertyValue::String(_) | - CameraPropertyValue::Array(_) => Some(Ordering::Greater), - CameraPropertyValue::EnumValue(_) => Some(Ordering::Equal), - _ => Some(Ordering::Less), - } - } - CameraPropertyValue::Binary(b) => { - match other { - CameraPropertyValue::Null| - CameraPropertyValue::Boolean(_)| - CameraPropertyValue::Integer(_)| - CameraPropertyValue::LongInteger(_)| - CameraPropertyValue::Float(_)| - CameraPropertyValue::Double(_)| - CameraPropertyValue::String(_)| - CameraPropertyValue::Array(_)| - CameraPropertyValue::EnumValue(_) => Some(Ordering::Greater), - CameraPropertyValue::Binary(ob) => { - b.partial_cmp(ob) - } - _ => Some(Ordering::Less), - } - } + CameraPropertyValue::Integer(oth) => Some(long.cmp(&(i128::from(oth)))), + CameraPropertyValue::LongInteger(o) => Some(long.cmp(o)), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::Float(fl) => match other { + CameraPropertyValue::Null + | CameraPropertyValue::Boolean(_) + | CameraPropertyValue::Integer(_) + | CameraPropertyValue::LongInteger(_) => Some(Ordering::Greater), + CameraPropertyValue::Float(f) => fl.partial_cmp(f), + CameraPropertyValue::Double(d) => f64::from(fl).partial_cmp(d), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::Double(d) => match other { + CameraPropertyValue::Null + | CameraPropertyValue::Boolean(_) + | CameraPropertyValue::Integer(_) + | CameraPropertyValue::LongInteger(_) => Some(Ordering::Greater), + CameraPropertyValue::Float(f) => d.partial_cmp(&(f64::from(f))), + CameraPropertyValue::Double(o) => d.partial_cmp(o), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::String(s) => match other { + CameraPropertyValue::Null + | CameraPropertyValue::Boolean(_) + | CameraPropertyValue::Integer(_) + | CameraPropertyValue::LongInteger(_) + | CameraPropertyValue::Float(_) + | CameraPropertyValue::Double(_) => Some(Ordering::Greater), + CameraPropertyValue::String(os) => s.partial_cmp(os), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::Array(a) => match other { + CameraPropertyValue::Null + | CameraPropertyValue::Boolean(_) + | CameraPropertyValue::Integer(_) + | CameraPropertyValue::LongInteger(_) + | CameraPropertyValue::Float(_) + | CameraPropertyValue::Double(_) + | CameraPropertyValue::String(_) => Some(Ordering::Greater), + CameraPropertyValue::Array(oa) => a.partial_cmp(oa), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::EnumValue(_) => match other { + CameraPropertyValue::Null + | CameraPropertyValue::Boolean(_) + | CameraPropertyValue::Integer(_) + | CameraPropertyValue::LongInteger(_) + | CameraPropertyValue::Float(_) + | CameraPropertyValue::Double(_) + | CameraPropertyValue::String(_) + | CameraPropertyValue::Array(_) => Some(Ordering::Greater), + CameraPropertyValue::EnumValue(_) => Some(Ordering::Equal), + _ => Some(Ordering::Less), + }, + CameraPropertyValue::Binary(b) => match other { + CameraPropertyValue::Null + | CameraPropertyValue::Boolean(_) + | CameraPropertyValue::Integer(_) + | CameraPropertyValue::LongInteger(_) + | CameraPropertyValue::Float(_) + | CameraPropertyValue::Double(_) + | CameraPropertyValue::String(_) + | CameraPropertyValue::Array(_) + | CameraPropertyValue::EnumValue(_) => Some(Ordering::Greater), + CameraPropertyValue::Binary(ob) => b.partial_cmp(ob), + _ => Some(Ordering::Less), + }, // FIXME: implement this lole CameraPropertyValue::Pair(_, _) => { // match other { @@ -655,7 +651,7 @@ macro_rules! define_back_and_fourth_control { impl ControlIntermediate { pub fn } - + // impl ControlIntermediate { // pub fn into_control(native_ctrl: $id_type) -> (crate::properties::CameraPropertyId, Option) { // match native_ctrl { diff --git a/nokhwa-core/src/ranges.rs b/nokhwa-core/src/ranges.rs index eb1f291..88fc142 100644 --- a/nokhwa-core/src/ranges.rs +++ b/nokhwa-core/src/ranges.rs @@ -1,9 +1,9 @@ -use std::collections::HashMap; -use core::fmt::{ Debug, Display, Formatter}; -use std::collections::hash_map::Keys; -use std::hash::Hash; -use std::ops::{Div, Sub}; use crate::error::NokhwaError; +use core::fmt::{Debug, Display, Formatter}; +use std::collections::hash_map::Keys; +use std::collections::HashMap; +use std::hash::Hash; +use std::ops::{Div, Rem, Sub}; /// Failed to validate. #[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)] @@ -22,27 +22,25 @@ pub trait ValidatableRange { /// /// Inclusive by default. #[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub struct Range -{ +pub struct Range { minimum: Option, lower_inclusive: bool, maximum: Option, upper_inclusive: bool, preferred: T, + step: Option, } -impl Range -where - T: Copy + Clone + Debug + PartialOrd + PartialEq, -{ +impl Range where T: Copy { /// Create an upper and lower inclusive [`Range`] - pub fn new(preferred: T, min: Option, max: Option) -> Self { + pub fn new(preferred: T, min: Option, max: Option, step: Option) -> Self { Self { minimum: min, lower_inclusive: true, maximum: max, upper_inclusive: true, preferred, + step, } } @@ -52,6 +50,7 @@ where lower_inclusive: bool, max: Option, upper_inclusive: bool, + step: Option ) -> Self { Self { minimum: min, @@ -59,6 +58,7 @@ where maximum: max, upper_inclusive, preferred, + step, } } @@ -69,6 +69,7 @@ where maximum: None, upper_inclusive: true, preferred, + step: None, } } @@ -84,6 +85,9 @@ where pub fn set_upper_inclusive(&mut self, upper_inclusive: bool) { self.upper_inclusive = upper_inclusive; } + pub fn set_step(&mut self, step: T) { + self.step = Some(step); + } pub fn set_preferred(&mut self, preferred: T) { self.preferred = preferred; } @@ -102,13 +106,27 @@ where pub fn preferred(&self) -> T { self.preferred } + pub fn step(&self) -> Option { + self.step + } } -impl ValidatableRange for Range where T: PartialEq + PartialOrd { +impl ValidatableRange for Range +where + T: SimpleRangeItem, +{ type Validation = T; - fn validate(&self, value: Self::Validation) -> Result<(), RangeValidationFailure> { - num_range_validate(self.minimum.as_ref(), self.maximum.as_ref(), &self.preferred, self.lower_inclusive, self.upper_inclusive, &value) + fn validate(&self, value: &T) -> Result<(), RangeValidationFailure> { + num_range_validate( + self.minimum, + self.maximum, + self.preferred, + self.lower_inclusive, + self.upper_inclusive, + self.step, + *value, + ) } } @@ -123,107 +141,39 @@ where maximum: None, upper_inclusive: true, preferred: T::default(), + step: None, } } } -impl Display for Range where T: Debug { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let lower_inclusive_char = bool_to_inclusive_char(self.lower_inclusive, false); - let upper_inclusive_char = bool_to_inclusive_char(self.upper_inclusive, true); - let default = default_to_string(&self.preferred); - - write!(f, "Range: {lower_inclusive_char}{}, {}{upper_inclusive_char}, Preferred: {default}", self.minimum, self.maximum) - } -} - - -#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] -pub struct IndicatedRange where T: Copy + Clone + Debug + PartialOrd + PartialEq { - minimum: T, - lower_inclusive: bool, - maximum: T, - upper_inclusive: bool, - step: Option, - default: Option, -} - -impl IndicatedRange +impl Display for Range where - T: Copy + Clone + Debug + PartialOrd + PartialEq + T: Debug, { - pub fn new(minimum: T, lower_inclusive: bool, maximum: T, upper_inclusive: bool, step: Option, default: Option) -> Self { - Self { minimum, lower_inclusive, maximum, upper_inclusive, step, default } - } - - pub fn minimum(&self) -> T { - self.minimum - } - - pub fn lower_inclusive(&self) -> bool { - self.lower_inclusive - } - - pub fn maximum(&self) -> T { - self.maximum - } - - pub fn upper_inclusive(&self) -> bool { - self.upper_inclusive - } - - pub fn step(&self) -> Option { - self.step - } - - pub fn default_value(&self) -> Option { - self.default - } -} - -impl ValidatableRange for IndicatedRange where T: Copy + PartialEq + PartialOrd + Div + Sub + Number { - type Validation = T; - - fn validate(&self, value: &Self::Validation) -> Result<(), RangeValidationFailure> { - if let Some(step) = &self.step { - let prepared_value = value - &self.minimum; - // We can check the step if we subtract the value from the minimum value - // then see if the remainder of prepared value and step is zero. - // e.g. 4, 12, value is 7, step is 3 - // 7 - 4 = 3 - // 3 % 3 = 0 Valid! - if prepared_value % step != 0 { - return Err(RangeValidationFailure::default()) - } - } - - num_range_validate(self.minimum.as_ref(), self.maximum.as_ref(), &self.default, self.lower_inclusive, self.upper_inclusive, &value) - } -} - -impl Display for IndicatedRange where T: Debug { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let lower_inclusive_char = bool_to_inclusive_char(self.lower_inclusive, false); let upper_inclusive_char = bool_to_inclusive_char(self.upper_inclusive, true); - let default = default_to_string(&self.default); - let step = default_to_string(&self.step); + let default = &self.preferred; - // Ex) IndicatedRange: (5, 19], Step: 3, Default: 8 - write!(f, "IndicatedRange: {lower_inclusive_char}{}, {}{upper_inclusive_char}, Step: {step}, Default: {default}", self.minimum, self.maximum) + write!( + f, + "Range: {lower_inclusive_char}{:?}, {:?}{upper_inclusive_char}, Preferred: {default:?}", + self.minimum, self.maximum + ) } } #[derive(Clone, Debug)] -pub struct Options where T: Clone + Debug { +pub struct Options { default: Option, available: Vec, } impl Options where - T: Clone + Debug + PartialEq + T: Clone + Debug + PartialEq, { - pub fn new(values: Vec, default_value: T) -> Self { + pub fn new(values: Vec, default_value: Option) -> Self { Self { default: default_value, available: values, @@ -239,7 +189,10 @@ where } } -impl ValidatableRange for Options where T: PartialEq { +impl ValidatableRange for Options +where + T: Clone + PartialEq, +{ type Validation = T; fn validate(&self, value: &Self::Validation) -> Result<(), RangeValidationFailure> { @@ -250,40 +203,53 @@ impl ValidatableRange for Options where T: PartialEq { } } -impl Display for Options where T: Debug { +impl Display for Options +where + T: Debug, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let default = default_to_string(&self.default); - write!(f, "Options: Available {:?}, Default: {default}", self.available) + write!( + f, + "Options: Available {:?}, Default: {default}", + self.available + ) } } #[derive(Clone, Debug)] -pub struct KeyValue where K: Clone + Debug + Hash + Eq, V: Clone + Debug { +pub struct KeyValue +where + K: Clone + Debug + Hash + Eq, + V: Clone + Debug, +{ defaults: HashMap, } impl KeyValue where K: Clone + Debug + Hash + Eq, - V: Clone + Debug + V: Clone + Debug, { pub fn new(default: HashMap) -> Self { - Self { - defaults: default, - } + Self { defaults: default } } - pub fn available_keys(&self) -> &Keys<'_, K, V> { - &self.defaults.keys() + pub fn available_keys(&self) -> Keys<'_, K, V> { + self.defaults.keys() } - + pub fn by_key(&self, key: &K) -> Option<&V> { self.defaults.get(key) } } -impl Display for KeyValue where K: Debug, V: Debug { +impl Display for KeyValue +where + K: Clone + Debug + Hash + Eq, + V: Clone + Debug, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // TODO: pretty print? write!(f, "Key Value Pairs: {:?}", self.defaults) @@ -291,16 +257,19 @@ impl Display for KeyValue where K: Debug, V: Debug { } #[derive(Clone, Debug)] -pub struct ArrayRange where T: Clone + Debug { +pub struct ArrayRange { appendable_options: Vec, default_options: Vec, } -impl ArrayRange where T: Clone + Debug + PartialEq { +impl ArrayRange +where + T: Clone + Debug + PartialEq, +{ pub fn new(appendable: Vec, default: Vec) -> Result { for option in &default { if !appendable.contains(option) { - return Err(NokhwaError::StructureError { structure: "ArrayRange".to_string(), error: "Attempted to add an undependable option to default option - ILLEGAL! - If you got this while using a driver, this is a bug! Please report to https://github.com/l1npengtul/nokhwa/issues!".to_string() }) + return Err(NokhwaError::StructureError { structure: "ArrayRange".to_string(), error: "Attempted to add an undependable option to default option - ILLEGAL! - If you got this while using a driver, this is a bug! Please report to https://github.com/l1npengtul/nokhwa/issues!".to_string() }); } } @@ -319,7 +288,10 @@ impl ArrayRange where T: Clone + Debug + PartialEq { } } -impl ValidatableRange for ArrayRange where T: PartialEq { +impl ValidatableRange for ArrayRange +where + T: PartialEq, +{ type Validation = T; fn validate(&self, value: &Self::Validation) -> Result<(), RangeValidationFailure> { @@ -330,22 +302,30 @@ impl ValidatableRange for ArrayRange where T: PartialEq { } } -impl Display for ArrayRange where T: Debug { +impl Display for ArrayRange +where + T: Debug, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "ArrayRange: Available Options: {:?}, Default: {:?}", self.appendable_options, self.default_options) + write!( + f, + "ArrayRange: Available Options: {:?}, Default: {:?}", + self.appendable_options, self.default_options + ) } } #[derive(Clone, Debug)] -pub struct Simple where T: Clone + Debug { - default: Option +pub struct Simple { + default: Option, } -impl Simple where T: Clone + Debug { +impl Simple +where + T: Clone + Debug, +{ pub fn new(default: Option) -> Self { - Self { - default, - } + Self { default } } pub fn default_value(&self) -> Option<&T> { @@ -361,7 +341,10 @@ impl ValidatableRange for Simple { } } -impl Display for Simple where T: Debug { +impl Display for Simple +where + T: Debug, +{ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let default = default_to_string(&self.default); write!(f, "Simple (Any Value): Default Value: {default}") @@ -370,12 +353,27 @@ impl Display for Simple where T: Debug { fn bool_to_inclusive_char(inclusive: bool, upper: bool) -> char { match inclusive { - true => if upper { ']' } else { '[' }, - false => if upper { ')' } else { '(' }, + true => { + if upper { + ']' + } else { + '[' + } + } + false => { + if upper { + ')' + } else { + '(' + } + } } } -fn default_to_string(default: &Option) -> String where T: Debug { +fn default_to_string(default: &Option) -> String +where + T: Debug, +{ match default { Some(v) => { format!("{v:?}") @@ -384,10 +382,33 @@ fn default_to_string(default: &Option) -> String where T: Debug { } } +fn num_range_validate( + minimum: Option, + maximum: Option, + default: T, + lower_inclusive: bool, + upper_inclusive: bool, + step: Option, + value: T, +) -> Result<(), RangeValidationFailure> +where + T: SimpleRangeItem, +{ + + if let (Some(step), Some(min)) = (step, minimum) { + let prepared_value: T = value - min; + // We can check the step if we subtract the value from the minimum value + // then see if the remainder of prepared value and step is zero. + // e.g. 4, 12, value is 7, step is 3 + // 7 - 4 = 3 + // 3 % 3 = 0 Valid! + if prepared_value % step != T::ZERO { + return Err(RangeValidationFailure::default()); + } + } -fn num_range_validate(minimum: Option<&T>, maximum: Option<&T>, default: &T, lower_inclusive: bool, upper_inclusive: bool, value: &T) -> Result<(), RangeValidationFailure> where T: PartialEq + PartialOrd { if value == default { - return Ok(()) + return Ok(()); } if let Some(min) = minimum { @@ -415,16 +436,24 @@ fn num_range_validate(minimum: Option<&T>, maximum: Option<&T>, default: &T, Ok(()) } -trait Number {} - -macro_rules! impl_num { - ( $($n:ty, )* ) => { - { - $( - impl Number for $n {} - )* - } - }; +pub trait SimpleRangeItem: Copy + Clone + Debug + Div + Sub + Rem + PartialOrd + PartialEq { + const ZERO: Self; } -impl_num!( i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, f32, f64, ); +macro_rules! impl_num { + ($($n:ty)*) => ($( + impl SimpleRangeItem for $n { + const ZERO: $n = 0; + } + )*) +} + +impl_num! { i8 u8 i16 u16 i32 u32 i64 u64 i128 u128 } + +impl SimpleRangeItem for f32 { + const ZERO: Self = 0_f32; +} + +impl SimpleRangeItem for f64 { + const ZERO: Self = 0_f64; +} \ No newline at end of file diff --git a/nokhwa-core/src/stream.rs b/nokhwa-core/src/stream.rs index fa85c86..77187ae 100644 --- a/nokhwa-core/src/stream.rs +++ b/nokhwa-core/src/stream.rs @@ -1,43 +1,86 @@ -use std::{ - future::Future, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll}, - sync::mpsc::Receiver -}; -use futures::Stream as AsyncStreamTrait; -use crate::{ - buffer::Buffer, - error::NokhwaError -}; +use crate::error::{NokhwaError, NokhwaResult}; +use crate::frame_buffer::FrameBuffer; +use flume::Receiver; +use futures::TryFutureExt; +use std::sync::Arc; -#[derive(Clone, Debug)] -pub enum ChannelState { - Frame(Buffer), - Error(NokhwaError), - ClosedWithError(NokhwaError), - Closed, +pub trait StreamInnerTrait { + fn receiver(&self) -> Arc>; + fn stop(&mut self) -> NokhwaResult<()>; } -pub enum StreamType { - Channel(Arc>), - Callback(Arc>>), +pub struct Stream { + inner: Box, } -pub struct CaptureStream {} +impl Stream { + pub fn new(inner: Box) -> Self { + Self { + inner, + } + } -impl Future for CaptureStream { - type Output = ChannelState; + // pub unsafe fn erase_lifetime(self) -> Stream<'static> { + // Self { + // inner: self.inner, + // phantom_data: Default::default(), + // } + // } - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - todo!() + pub fn poll_frame(&self) -> NokhwaResult { + if self.inner.receiver().is_disconnected() { + return Err(NokhwaError::ReadFrameError( + "stream is disconnected!".to_string(), + )); + } + + self.inner + .receiver() + .recv() + .map_err(|why| NokhwaError::ReadFrameError(why.to_string())) + } + + pub fn try_poll_frame(&self) -> Option> { + if self.inner.receiver().is_disconnected() { + return Some(Err(NokhwaError::ReadFrameError( + "stream is disconnected!".to_string(), + ))); + } + + if self.inner.receiver().is_empty() { + return None; + } + + Some( + self.inner + .receiver() + .try_recv() + .map_err(|why| NokhwaError::ReadFrameError(why.to_string())), + ) + } + + #[cfg(feature = "async")] + pub async fn await_frame(&self) -> NokhwaResult { + if self.inner.receiver().is_disconnected() { + return Err(NokhwaError::ReadFrameError( + "stream is disconnected!".to_string(), + )); + } + + self.inner + .receiver() + .recv_async() + .map_err(|why| NokhwaError::ReadFrameError(why.to_string())).await + } + + pub fn stop_stream(mut self) -> NokhwaResult<()> { + self.inner.stop()?; + Ok(()) } } -impl AsyncStreamTrait for CaptureStream { - type Item = ChannelState; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - todo!() +impl Drop for Stream { + fn drop(&mut self) { + let _ = self.inner.stop(); } -} \ No newline at end of file +} diff --git a/nokhwa-core/src/traits.rs b/nokhwa-core/src/traits.rs index c7ed4fb..7ade313 100644 --- a/nokhwa-core/src/traits.rs +++ b/nokhwa-core/src/traits.rs @@ -15,7 +15,3 @@ */ // pub trait VirtualBackendTrait {} - -pub trait Distance where T: PartialEq { - fn distance_from(&self, other: &Self) -> T; -} diff --git a/nokhwa-core/src/types.rs b/nokhwa-core/src/types.rs index f7122ac..7ea115d 100644 --- a/nokhwa-core/src/types.rs +++ b/nokhwa-core/src/types.rs @@ -1,18 +1,18 @@ -use crate::{ - error::NokhwaError, - frame_format::FrameFormat, -}; +use crate::utils::Distance; +use crate::{error::NokhwaError, frame_format::FrameFormat}; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; use std::{ - borrow::Borrow, cmp::Ordering, fmt::{ - Debug, - Display, - Formatter - }, hash::{Hash, Hasher}, ops::{Add, Deref, DerefMut, Sub} + borrow::Borrow, + cmp::Ordering, + fmt::{Debug, Display, Formatter}, + hash::{Hash}, + ops::{Sub}, }; -use crate::traits::Distance; - +use std::num::NonZeroI32; +use std::ops::{Div, Rem}; +use num_rational::Rational32; +use crate::ranges::{SimpleRangeItem}; /// Describes the index of the camera. /// - Index: A numbered index @@ -105,7 +105,7 @@ pub struct Resolution { impl Resolution { /// Create a new resolution from 2 image size coordinates. #[must_use] - pub fn new(x: u32, y: u32) -> Self { + pub const fn new(x: u32, y: u32) -> Self { Resolution { width_x: x, height_y: y, @@ -180,78 +180,123 @@ impl Distance for Resolution { } } -#[derive(Copy, Clone, Debug, Hash)] +impl Div for Resolution { + type Output = Resolution; + + fn div(self, rhs: Self) -> Self::Output { + let x_div = self.x().div(rhs.x()); + let y_div = self.y().div(rhs.y()); + Resolution::new(x_div, y_div) + } +} + +impl Sub for Resolution { + type Output = Resolution; + + fn sub(self, rhs: Self) -> Self::Output { + let x_sub = self.x().sub(rhs.x()); + let y_sub = self.y().sub(rhs.y()); + Resolution::new(x_sub, y_sub) + } +} + +impl Rem for Resolution { + type Output = Resolution; + + fn rem(self, rhs: Self) -> Self::Output { + let x_rem = self.x().rem(rhs.x()); + let y_rem = self.y().rem(rhs.y()); + Resolution::new(x_rem, y_rem) + } +} + +impl SimpleRangeItem for Resolution { + const ZERO: Self = Resolution::new(0, 0); +} + +/// Framerate of a camera, backed by a num-rational Ratio type. +/// +/// Note that while constructing negative is allowed, the absolute value +/// will be passed to the driver. +/// +/// # Panics +/// If denominator is 0, any attempt to use the [`FrameRate`] will **panic.** +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] pub struct FrameRate { - numerator: u32, - denominator: u32 + rational: Rational32, } impl FrameRate { - pub fn new(numerator: u32, denominator: u32) -> Self { + pub const fn new(numerator: i32, denominator: NonZeroI32) -> Self { Self { - numerator, - denominator, + rational: Rational32::new_raw(numerator, denominator.get()), } } - pub fn frame_rate(fps: u32) -> Self { + pub const fn frame_rate(fps: i32) -> Self { Self { - numerator: fps, - denominator: 1, + rational: Rational32::new_raw(fps, 1), } } - pub fn numerator(&self) -> u32 { - self.numerator + pub fn numerator(&self) -> &i32 { + self.rational.numer() } - pub fn denominator(&self) -> u32 { - self.denominator + pub fn denominator(&self) -> &i32 { + self.rational.denom() } - } impl Default for FrameRate { fn default() -> Self { - FrameRate::new(30, 1) + FrameRate::new(30, NonZeroI32::new(1).unwrap()) } } impl Display for FrameRate { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}/{} FPS", self.numerator, self.denominator) + write!(f, "{}/{} FPS", self.numerator(), self.denominator()) } } -impl Eq for FrameRate {} +impl Div for FrameRate { + type Output = FrameRate; -impl PartialEq for FrameRate { - fn eq(&self, other: &Self) -> bool { - (self.numerator * other.denominator) == (other.numerator * self.numerator) + fn div(self, rhs: Self) -> Self::Output { + self.rational.div(rhs.rational).into() } } -impl PartialOrd for FrameRate { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl Sub for FrameRate { + type Output = FrameRate; + + fn sub(self, rhs: Self) -> Self::Output { + self.rational.sub(rhs.rational).into() } } -impl Ord for FrameRate { - fn cmp(&self, other: &Self) -> Ordering { - if self.numerator == 0 && other.denominator == 0 { - return Ordering::Equal; +impl Rem for FrameRate { + type Output = FrameRate; + + fn rem(self, rhs: Self) -> Self::Output { + self.rational.rem(rhs.rational).into() + } +} + +impl SimpleRangeItem for FrameRate { + const ZERO: Self = FrameRate::frame_rate(0); +} + +impl From for FrameRate { + fn from(value: Rational32) -> Self { + FrameRate { + rational: value, } - - if self.denominator == other.denominator { - return self.numerator.cmp(&other.numerator); - } - - (self.numerator * other.denominator).cmp(&(other.numerator * self.denominator)) } } - /// This is a convenience struct that holds all information about the format of a webcam stream. /// It consists of a [`Resolution`], [`FrameFormat`], and a [`FrameRate`]. #[derive(Copy, Clone, Debug, Hash, PartialEq, PartialOrd)] @@ -265,7 +310,7 @@ pub struct CameraFormat { impl CameraFormat { /// Construct a new [`CameraFormat`] #[must_use] - pub fn new(resolution: Resolution, format: FrameFormat, frame_rate: FrameRate) -> Self { + pub const fn new(resolution: Resolution, format: FrameFormat, frame_rate: FrameRate) -> Self { CameraFormat { resolution, format, @@ -275,7 +320,7 @@ impl CameraFormat { /// [`CameraFormat::new()`], but raw. #[must_use] - pub fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: FrameRate) -> Self { + pub const fn new_from(res_x: u32, res_y: u32, format: FrameFormat, fps: FrameRate) -> Self { CameraFormat { resolution: Resolution { width_x: res_x, @@ -530,374 +575,3 @@ impl Display for ApiBackend { write!(f, "{self:?}") } } - -// /// 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 { -// match self { -// CameraIndex::Index(i) => Ok(*i), -// CameraIndex::String(s) => match s.parse::() { -// 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 for CameraIndex { -// fn from(v: u32) -> Self { -// CameraIndex::Index(v) -// } -// } - -// /// Trait for strings that can be converted to [`CameraIndex`]es. -// pub trait ValidString: AsRef {} -// -// 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 From for CameraIndex -// where -// T: ValidString, -// { -// fn from(v: T) -> Self { -// CameraIndex::String(v.as_ref().to_string()) -// } -// } - -#[cfg(all(feature = "conversions", not(target_arch = "wasm32")))] -#[cfg_attr(feature = "docs-features", doc(cfg(feature = "mjpeg")))] -#[inline] -fn decompress<'a>( - data: &'a [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(), - }) - } - } -} - -/// Converts a MJpeg stream of `&[u8]` into a `Vec` 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 = "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, NokhwaError> { - let mut jpeg_decompress = decompress(data, rgba)?; - - let scanlines_res: Option> = jpeg_decompress.read_scanlines_flat(); - // assert!(jpeg_decompress.finish_decompress()); - if !jpeg_decompress.finish_decompress() { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJpeg, - destination: "RGB888".to_string(), - error: "JPEG Decompressor did not finish.".to_string(), - }); - } - - 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(), - }), - } -} - - -/// 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, NokhwaError> { - Err(NokhwaError::NotImplementedError( - "Not available on WASM".to_string(), - )) -} - -/// 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"))))] -#[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 = mozjpeg::decompress(data, rgba)?; - - // assert_eq!(dest.len(), jpeg_decompress.min_flat_buffer_size()); - if dest.len() != jpeg_decompress.min_flat_buffer_size() { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJpeg, - destination: "RGB888".to_string(), - error: "Bad decoded buffer size".to_string(), - }); - } - - jpeg_decompress.read_scanlines_flat_into(dest); - // assert!(jpeg_decompress.finish_decompress()); - if !jpeg_decompress.finish_decompress() { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::MJpeg, - destination: "RGB888".to_string(), - error: "JPEG Decompressor did not finish.".to_string(), - }); - } - Ok(()) -} - -// 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(), - )) -} - -/// Returns the predicted size of the destination Yuv422422 buffer. -#[inline] -pub fn yuyv422_predicted_size(size: usize, rgba: bool) -> usize { - let pixel_size = if rgba { 4 } else { 3 }; - // yuyv yields 2 3-byte pixels per yuyv chunk - (size / 4) * (2 * pixel_size) -} - -#[inline] -pub fn yuyv422_to_rgb(data: &[u8], rgba: bool) -> Result, NokhwaError> { - let capacity = yuyv422_predicted_size(data.len(), rgba); - let mut rgb = vec![0; capacity]; - buf_yuyv422_to_rgb(data, &mut rgb, rgba)?; - Ok(rgb) -} - -/// Same as [`yuyv422_to_rgb`] but with a destination buffer instead of a return `Vec` -/// # Errors -/// If the stream is invalid Yuv422, or the destination buffer is not large enough, this will error. -#[inline] -pub fn buf_yuyv422_to_rgb(data: &[u8], dest: &mut [u8], rgba: bool) -> Result<(), NokhwaError> { - let mut buf: Vec = Vec::new(); - if data.len() % 4 != 0 { - return Err(NokhwaError::ProcessFrameError { - 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 = 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.); - let b0 = y0 + 1.732_446 * (u - 128.); - - let r1 = y1 + 1.370_705 * (v - 128.); - let g1 = y1 - 0.698_001 * (v - 128.) - 0.337_633 * (u - 128.); - let b1 = y1 + 1.732_446 * (u - 128.); - - if rgba { - buf.extend_from_slice(&[ - r0 as u8, g0 as u8, b0 as u8, 255, r1 as u8, g1 as u8, b1 as u8, 255, - ]); - } else { - buf.extend_from_slice(&[r0 as u8, g0 as u8, b0 as u8, r1 as u8, g1 as u8, b1 as u8]); - } - } - dest.copy_from_slice(&buf); - 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] -} - -// equation from https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB -/// Convert `YCbCr` 4:4:4 to a RGBA8888. [For further reading](https://en.wikipedia.org/wiki/YUV#Converting_between_Y%E2%80%B2UV_and_RGB) -/// -/// Equivalent to [`yuyv444_to_rgb`] but with an alpha channel attached. -#[allow(clippy::many_single_char_names)] -#[must_use] -#[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] -} - -/// Converts a Yuv422 4:2:0 bi-planar (NV12) 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 wrong. -#[inline] -pub fn nv12_to_rgb( - resolution: Resolution, - data: &[u8], - rgba: bool, -) -> Result, NokhwaError> { - let pxsize = if rgba { 4 } else { 3 }; - let mut dest = vec![0; (pxsize * resolution.width() * resolution.height()) as usize]; - buf_nv12_to_rgb(resolution, data, &mut dest, rgba)?; - Ok(dest) -} - -// this depresses me -// like, everytime i open this codebase all the life is sucked out of me -// i hate it -/// Converts a Yuv422 4:2:0 bi-planar (NV12) datastream to a RGB888 Stream and outputs it into a destination buffer. [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 wrong. -#[allow(clippy::similar_names)] -#[inline] -pub fn buf_nv12_to_rgb( - resolution: Resolution, - data: &[u8], - out: &mut [u8], - rgba: bool, -) -> Result<(), NokhwaError> { - if resolution.width() % 2 != 0 || resolution.height() % 2 != 0 { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::Nv12, - destination: "RGB".to_string(), - error: "bad resolution".to_string(), - }); - } - - if data.len() != ((resolution.width() * resolution.height() * 3) / 2) as usize { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::Nv12, - destination: "RGB".to_string(), - error: "bad input buffer size".to_string(), - }); - } - - let pxsize = if rgba { 4 } else { 3 }; - - if out.len() != (pxsize * resolution.width() * resolution.height()) as usize { - return Err(NokhwaError::ProcessFrameError { - src: FrameFormat::Nv12, - destination: "RGB".to_string(), - error: "bad output buffer size".to_string(), - }); - } - - let rgba_size = if rgba { 4 } else { 3 }; - - let y_section = (resolution.width() * resolution.height()) as usize; - - let width_usize = resolution.width() as usize; - // let height_usize = resolution.height() as usize; - - for (hidx, horizontal_row) in data[0..y_section].chunks_exact(width_usize).enumerate() { - for (cidx, column) in horizontal_row.chunks_exact(2).enumerate() { - let u = data[(y_section) + ((hidx / 2) * width_usize) + (cidx * 2)]; - let v = data[(y_section) + ((hidx / 2) * width_usize) + (cidx * 2) + 1]; - - let y0 = column[0]; - let y1 = column[1]; - let base_index = (hidx * width_usize * rgba_size) + cidx * rgba_size * 2; - - if rgba { - let px0 = yuyv444_to_rgba(y0 as i32, u as i32, v as i32); - let px1 = yuyv444_to_rgba(y1 as i32, u as i32, v as i32); - - out[base_index] = px0[0]; - out[base_index + 1] = px0[1]; - out[base_index + 2] = px0[2]; - out[base_index + 3] = px0[3]; - out[base_index + 4] = px1[0]; - out[base_index + 5] = px1[1]; - out[base_index + 6] = px1[2]; - out[base_index + 7] = px1[3]; - } else { - let px0 = yuyv444_to_rgb(y0 as i32, u as i32, v as i32); - let px1 = yuyv444_to_rgb(y1 as i32, u as i32, v as i32); - - out[base_index] = px0[0]; - out[base_index + 1] = px0[1]; - out[base_index + 2] = px0[2]; - out[base_index + 3] = px1[0]; - out[base_index + 4] = px1[1]; - out[base_index + 5] = px1[2]; - } - } - } - - Ok(()) -} diff --git a/nokhwa-core/src/utils.rs b/nokhwa-core/src/utils.rs index 8080fe9..702afd8 100644 --- a/nokhwa-core/src/utils.rs +++ b/nokhwa-core/src/utils.rs @@ -1,6 +1,10 @@ use core::ops::AddAssign; -pub fn min_max_range + Sized>(min: N, max: N, step: N) -> Vec { +pub fn min_max_range + Sized>( + min: N, + max: N, + step: N, +) -> Vec { let mut counter = min; let mut nums = vec![min]; @@ -8,7 +12,7 @@ pub fn min_max_range + Sized>(min: N, max: N counter += step; if counter > max { - break + break; } nums.push(counter); @@ -16,3 +20,10 @@ pub fn min_max_range + Sized>(min: N, max: N nums } + +pub trait Distance +where + T: PartialEq, +{ + fn distance_from(&self, other: &Self) -> T; +} diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index a3e7e35..38e97e9 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -21,7 +21,7 @@ use nokhwa_bindings_macos::{ AVCaptureVideoDataOutput, }; use nokhwa_core::{ - buffer::Buffer, + frame_buffer::FrameBuffer, error::NokhwaError, pixel_format::RgbFormat, traits::CaptureTrait, @@ -278,11 +278,11 @@ impl CaptureTrait for AVFoundationCaptureDevice { } } - fn frame(&mut self) -> Result { + fn frame(&mut self) -> Result { self.refresh_camera_format()?; let cfmt = self.camera_format(); let b = self.frame_raw()?; - let buffer = Buffer::new(cfmt.resolution(), b.as_ref(), cfmt.format()); + let buffer = FrameBuffer::new(cfmt.resolution(), b.as_ref(), cfmt.format()); let _ = self.frame_buffer_receiver.drain(); Ok(buffer) } @@ -476,7 +476,7 @@ impl CaptureTrait for AVFoundationCaptureDevice { todo!() } - fn frame(&mut self) -> Result { + fn frame(&mut self) -> Result { todo!() } diff --git a/src/backends/capture/browser_camera.rs b/src/backends/capture/browser_camera.rs index 381283c..1b74d09 100644 --- a/src/backends/capture/browser_camera.rs +++ b/src/backends/capture/browser_camera.rs @@ -6,7 +6,7 @@ use nokhwa_core::format_request::FormatRequest; use serde::{de, Serialize}; use wasm_bindgen_futures::JsFuture; use web_sys::{window, MediaDeviceInfo, MediaDevices, MediaStream, MediaStreamConstraints, MediaStreamTrack, MediaTrackConstraints, Navigator}; -use nokhwa_core::buffer::Buffer; +use nokhwa_core::frame_buffer::FrameBuffer; use nokhwa_core::properties::{CameraControl, ControlValueSetter, KnownCameraControl}; use nokhwa_core::error::NokhwaError; use nokhwa_core::frame_format::FrameFormat; @@ -373,7 +373,7 @@ impl CaptureTrait for BrowserCaptureDevice { todo!() } - fn frame(&mut self) -> Result { + fn frame(&mut self) -> Result { todo!() } @@ -422,7 +422,7 @@ impl AsyncCaptureTrait for BrowserCaptureDevice { todo!() } - async fn frame_async(&mut self) -> Result { + async fn frame_async(&mut self) -> Result { todo!() } diff --git a/src/backends/capture/msmf_backend.rs b/src/backends/capture/msmf_backend.rs index 71d672d..cf20ffb 100644 --- a/src/backends/capture/msmf_backend.rs +++ b/src/backends/capture/msmf_backend.rs @@ -15,7 +15,7 @@ */ use nokhwa_bindings_windows::wmf::MediaFoundationDevice; use nokhwa_core::{ - buffer::Buffer, + frame_buffer::FrameBuffer, error::NokhwaError, pixel_format::RgbFormat, traits::CaptureTrait, @@ -242,10 +242,10 @@ impl CaptureTrait for MediaFoundationCaptureDevice { self.inner.is_stream_open() } - fn frame(&mut self) -> Result { + fn frame(&mut self) -> Result { self.refresh_camera_format()?; let self_ctrl = self.camera_format(); - Ok(Buffer::new( + Ok(FrameBuffer::new( self_ctrl.resolution(), &self.inner.raw_bytes()?, self_ctrl.format(), diff --git a/src/backends/capture/opencv_backend.rs b/src/backends/capture/opencv_backend.rs index f867c8f..75e200b 100644 --- a/src/backends/capture/opencv_backend.rs +++ b/src/backends/capture/opencv_backend.rs @@ -16,7 +16,7 @@ use nokhwa_core::types::RequestedFormatType; use nokhwa_core::{ - buffer::Buffer, + frame_buffer::FrameBuffer, error::NokhwaError, traits::CaptureTrait, types::{ @@ -525,7 +525,7 @@ impl CaptureTrait for OpenCvCaptureDevice { self.video_capture.is_opened().unwrap_or(false) } - fn frame(&mut self) -> Result { + fn frame(&mut self) -> Result { let camera_resolution = self.camera_format.resolution(); let image_data = { let mut data = self.frame_raw()?.to_vec(); @@ -535,7 +535,7 @@ impl CaptureTrait for OpenCvCaptureDevice { ); data }; - Ok(Buffer::new( + Ok(FrameBuffer::new( camera_resolution, &image_data, self.camera_format.format(), diff --git a/src/backends/capture/v4l2_backend.rs b/src/backends/capture/v4l2_backend.rs index 1331305..64b5237 100644 --- a/src/backends/capture/v4l2_backend.rs +++ b/src/backends/capture/v4l2_backend.rs @@ -13,7 +13,7 @@ use nokhwa_bindings_linux::{ } }; use nokhwa_core::{ - capture::{Open, Setting}, + camera::{Open, Setting}, error::{NokhwaError, NokhwaResult}, frame_format::FrameFormat, properties::CameraProperties, diff --git a/src/camera.rs b/src/camera.rs index cdd91cf..88d06f5 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -18,7 +18,7 @@ use nokhwa_core::format_request::FormatFilter; use nokhwa_core::frame_format::SourceFrameFormat; use nokhwa_core::traits::Backend; use nokhwa_core::{ - buffer::Buffer, + frame_buffer::FrameBuffer, error::NokhwaError, pixel_format::FormatDecoder, traits::CaptureTrait, @@ -136,7 +136,7 @@ impl CaptureTrait for Camera { todo!() } - fn frame(&mut self) -> Result { + fn frame(&mut self) -> Result { todo!() } diff --git a/src/lib.rs b/src/lib.rs index 4187a1f..1a20cbd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub mod threaded; pub use camera::Camera; pub use init::*; -pub use nokhwa_core::buffer::Buffer; +pub use nokhwa_core::frame_buffer::FrameBuffer; pub use nokhwa_core::error::NokhwaError; pub use query::*; #[cfg(feature = "output-threaded")] @@ -68,5 +68,5 @@ pub mod camera_traits { } pub mod buffer { - pub use nokhwa_core::buffer::*; + pub use nokhwa_core::frame_buffer::*; } diff --git a/src/threaded.rs b/src/threaded.rs index a15c453..03df237 100644 --- a/src/threaded.rs +++ b/src/threaded.rs @@ -16,7 +16,7 @@ use crate::Camera; use nokhwa_core::{ - buffer::Buffer, + frame_buffer::FrameBuffer, error::NokhwaError, types::{ ApiBackend, CameraFormat, CameraIndex, CameraInfo, @@ -36,11 +36,11 @@ use nokhwa_core::properties::{CameraControl, ControlValueSetter, KnownCameraCont type AtomicLock = Arc>; pub type CallbackFn = fn( _camera: &Arc>, - _frame_callback: &Arc>>>, - _last_frame_captured: &Arc>, + _frame_callback: &Arc>>>, + _last_frame_captured: &Arc>, _die_bool: &Arc, ); -type HeldCallbackType = Arc>>; +type HeldCallbackType = Arc>>; /// Creates a camera that runs in a different thread that you can use a callback to access the frames of. /// It uses a `Arc` and a `Mutex` to ensure that this feels like a normal camera, but callback based. @@ -58,7 +58,7 @@ type HeldCallbackType = Arc>>; pub struct CallbackCamera { camera: AtomicLock, frame_callback: HeldCallbackType, - last_frame_captured: AtomicLock, + last_frame_captured: AtomicLock, die_bool: Arc, current_camera: CameraInfo, handle: AtomicLock>>, @@ -72,7 +72,7 @@ impl CallbackCamera { pub fn new( index: CameraIndex, format: RequestedFormat, - callback: impl FnMut(Buffer) + Send + 'static, + callback: impl FnMut(FrameBuffer) + Send + 'static, ) -> Result { let arc_camera = Arc::new(Mutex::new(Camera::new(index, format)?)); let current_camera = arc_camera @@ -86,7 +86,7 @@ impl CallbackCamera { Ok(CallbackCamera { camera: arc_camera, frame_callback: Arc::new(Mutex::new(Box::new(callback))), - last_frame_captured: Arc::new(Mutex::new(Buffer::new( + last_frame_captured: Arc::new(Mutex::new(FrameBuffer::new( Resolution::new(0, 0), &vec![], FrameFormat::GRAY, @@ -100,12 +100,12 @@ impl CallbackCamera { /// Allows creation of a [`Camera`] with a custom backend. This is useful if you are creating e.g. a custom module. /// /// You **must** have set a format beforehand. - pub fn with_custom(camera: Camera, callback: impl FnMut(Buffer) + Send + 'static) -> Self { + pub fn with_custom(camera: Camera, callback: impl FnMut(FrameBuffer) + Send + 'static) -> Self { let current_camera = camera.info().clone(); CallbackCamera { camera: Arc::new(Mutex::new(camera)), frame_callback: Arc::new(Mutex::new(Box::new(callback))), - last_frame_captured: Arc::new(Mutex::new(Buffer::new( + last_frame_captured: Arc::new(Mutex::new(FrameBuffer::new( Resolution::new(0, 0), &vec![], FrameFormat::GRAY, @@ -183,7 +183,7 @@ impl CallbackCamera { *self .last_frame_captured .lock() - .map_err(|why| NokhwaError::GeneralError(why.to_string()))? = Buffer::new( + .map_err(|why| NokhwaError::GeneralError(why.to_string()))? = FrameBuffer::new( new_fmt.resolution(), &Vec::default(), self.camera_format()?.format(), @@ -266,7 +266,7 @@ impl CallbackCamera { .last_frame_captured .lock() .map_err(|why| NokhwaError::GeneralError(why.to_string()))? = - Buffer::new(new_res, &Vec::default(), self.camera_format()?.format()); + FrameBuffer::new(new_res, &Vec::default(), self.camera_format()?.format()); self.camera .lock() .map_err(|why| NokhwaError::SetPropertyError { @@ -479,7 +479,7 @@ impl CallbackCamera { /// Sets the frame callback to the new specified function. This function will be called instead of the previous one(s). pub fn set_callback( &mut self, - callback: impl FnMut(Buffer) + Send + 'static, + callback: impl FnMut(FrameBuffer) + Send + 'static, ) -> Result<(), NokhwaError> { *self .frame_callback @@ -494,7 +494,7 @@ impl CallbackCamera { /// Polls the camera for a frame, analogous to [`Camera::frame`](crate::Camera::frame) /// # Errors /// This will error if the camera fails to capture a frame. - pub fn poll_frame(&mut self) -> Result { + pub fn poll_frame(&mut self) -> Result { let frame = self .camera .lock() @@ -508,7 +508,7 @@ impl CallbackCamera { } /// Gets the last frame captured by the camera. - pub fn last_frame(&self) -> Result { + pub fn last_frame(&self) -> Result { Ok(self .last_frame_captured .lock() @@ -549,7 +549,7 @@ impl Drop for CallbackCamera { fn camera_frame_thread_loop( camera: AtomicLock, frame_callback: HeldCallbackType, - last_frame_captured: AtomicLock, + last_frame_captured: AtomicLock, die_bool: Arc, ) { loop {