nokhwa-iter helper crate
@@ -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", "nokhwa-decoders"]
|
||||
members = ["nokhwa-bindings-macos", "nokhwa-bindings-windows", "nokhwa-bindings-linux", "nokhwa-core", "nokhwa-decoders", "nokhwa-iter-extensions"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
use crate::frame_format::FrameFormat;
|
||||
use crate::frame_format::{CustomFrameFormat, FrameFormat};
|
||||
use std::fmt::Debug;
|
||||
use thiserror::Error;
|
||||
use crate::types::Backends;
|
||||
@@ -66,6 +66,8 @@ pub enum NokhwaError {
|
||||
Decoder(String),
|
||||
#[error("Unsupported FrameFormat: {0}")]
|
||||
DecoderUnsupportedFrameFormat(FrameFormat),
|
||||
#[error("The destination frame format from {0} to {1} is not supported.")]
|
||||
DecoderUnsupportedCustomFrameFormatDestination(CustomFrameFormat, FrameFormat),
|
||||
#[error("Unsupported pixel configuration {0} with width {1}b.")]
|
||||
DecoderUnsupportedDestinationPixelFormat(&'static str, u32),
|
||||
#[error("Bad decoder configuration: {0}")]
|
||||
@@ -79,7 +81,7 @@ pub enum NokhwaError {
|
||||
#[error("You need to pass in a destination hint, it is not optional for this decoder.")]
|
||||
DecoderDestinationHintRequired,
|
||||
#[error("Decoder already deinitialized. Unusable, please make a new decoder.")]
|
||||
DecoderAlreadyDeinitialized
|
||||
DecoderAlreadyDeinitialized,
|
||||
}
|
||||
//
|
||||
// pub enum InitializeError {}
|
||||
|
||||
@@ -205,13 +205,22 @@ define_frame_format_with_groups! {
|
||||
]
|
||||
}
|
||||
|
||||
impl FrameFormat {
|
||||
pub fn is_custom(&self) -> bool {
|
||||
if let FrameFormat::Custom(_) = self {
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FrameFormat {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialOrd, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum CustomFrameFormat {
|
||||
UUID(u128),
|
||||
@@ -222,6 +231,12 @@ pub enum CustomFrameFormat {
|
||||
F64(OrderedFloat<f64>),
|
||||
}
|
||||
|
||||
impl Display for CustomFrameFormat {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
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) => {
|
||||
|
||||
@@ -7,8 +7,9 @@ edition = "2024"
|
||||
ffmpeg = ["ffmpeg-the-third"]
|
||||
yuyv = ["dcv-color-primitives", "yuv"]
|
||||
mjpeg = ["zune-jpeg", "zune-core"]
|
||||
luma = ["itertools"]
|
||||
#static = ["ffmpeg-the-third/static"]
|
||||
async = []
|
||||
#async = []
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.23"
|
||||
@@ -37,6 +38,10 @@ optional = true
|
||||
version = "0.5.0-rc2"
|
||||
optional = true
|
||||
|
||||
[dependencies.itertools]
|
||||
version = "0.14.0"
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.image]
|
||||
workspace = true
|
||||
features = ["png", "jpeg", "avif"]
|
||||
|
||||
@@ -6,3 +6,5 @@ pub mod ffmpeg;
|
||||
pub mod mjpeg;
|
||||
#[cfg(feature = "yuyv")]
|
||||
pub mod yuv;
|
||||
#[cfg(feature = "luma")]
|
||||
pub mod luma;
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::mem::swap;
|
||||
use image::Pixel;
|
||||
use itertools::Itertools;
|
||||
use nokhwa_core::decoder::Decoder;
|
||||
use nokhwa_core::error::NokhwaError;
|
||||
use nokhwa_core::frame_buffer::FrameBuffer;
|
||||
use nokhwa_core::frame_format::{CustomFrameFormat, FrameFormat};
|
||||
use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth};
|
||||
use nokhwa_core::types::{CameraFormat, Resolution};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LumaDecoder {
|
||||
luma_config: LumaConfig,
|
||||
}
|
||||
|
||||
impl Decoder for LumaDecoder {
|
||||
type Config = LumaConfig;
|
||||
type OutputMeta = ();
|
||||
type DestinationFormatHint = LumaDestination;
|
||||
|
||||
fn config(&self) -> &Self::Config {
|
||||
&self.luma_config
|
||||
}
|
||||
|
||||
fn set_config(&mut self, config: Self::Config) -> Result<(), NokhwaError> {
|
||||
self.luma_config = config;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_to_buffer(&mut self, mut to_decode: FrameBuffer, mut buffer: impl AsMut<[u8]>, destination_format_hint: Option<Self::DestinationFormatHint>) -> Result<Self::OutputMeta, NokhwaError> {
|
||||
let destination_hint = match destination_format_hint {
|
||||
Some(h) => h,
|
||||
None => return Err(NokhwaError::DecoderDestinationHintRequired)
|
||||
};
|
||||
|
||||
let (width, max_value) = match self.config().format {
|
||||
FrameFormat::Luma_8 => (1, u8::MAX as u32),
|
||||
FrameFormat::Luma_10 => (2, 2_u32.pow(10)),
|
||||
FrameFormat::Luma_12 => (2, 2_u32.pow(12)),
|
||||
FrameFormat::Luma_14 => (2, 2_u32.pow(14)),
|
||||
FrameFormat::Luma_16 | FrameFormat::Depth_16 => (2, u16::MAX as u32),
|
||||
fmt => return Err(NokhwaError::DecoderUnsupportedFrameFormat(fmt))
|
||||
};
|
||||
|
||||
let format = self.config().custom_frame_format_map.as_ref().map(|m| {
|
||||
match self.config().format {
|
||||
FrameFormat::Custom(cfmt) => {
|
||||
m.get(&cfmt).copied()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}).flatten().unwrap_or(self.config().format);
|
||||
|
||||
let r = filter_to_u8(self.config().channel_filters.red);
|
||||
let g = filter_to_u8(self.config().channel_filters.green);
|
||||
let b = filter_to_u8(self.config().channel_filters.blue);
|
||||
let a = filter_to_u8(self.config().channel_filters.alpha);
|
||||
|
||||
let r_u16 = filter_to_u16(self.config().channel_filters.red);
|
||||
let g_u16 = filter_to_u16(self.config().channel_filters.green);
|
||||
let b_u16 = filter_to_u16(self.config().channel_filters.blue);
|
||||
let a_u16 = filter_to_u16(self.config().channel_filters.alpha);
|
||||
|
||||
match format {
|
||||
FrameFormat::Luma_8 => {
|
||||
match destination_hint {
|
||||
LumaDestination::Luma8 => {
|
||||
swap(to_decode.as_mut(), buffer.as_mut())
|
||||
}
|
||||
LumaDestination::LumaA8 => {
|
||||
let default_alpha = u8::MAX * a;
|
||||
|
||||
to_decode.buffer().into_iter().intersperse(default_alpha).co
|
||||
}
|
||||
LumaDestination::Rgb8 => {}
|
||||
LumaDestination::Rgba8 => {}
|
||||
LumaDestination::Rgb16 => {}
|
||||
LumaDestination::Rgba16 => {}
|
||||
}
|
||||
}
|
||||
FrameFormat::Luma_10 => {}
|
||||
FrameFormat::Luma_12 => {}
|
||||
FrameFormat::Luma_14 => {}
|
||||
FrameFormat::Luma_16 | FrameFormat::Depth_16 => {}
|
||||
fmt => {
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(fmt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_to_pixel_buffer<P: Pixel>(&mut self, to_decode: FrameBuffer, buffer: impl AsMut<[P::Subpixel]>) -> Result<Self::OutputMeta, NokhwaError>
|
||||
where
|
||||
<P as Pixel>::Subpixel: NonFloatScalarWidth
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn decode<P: Pixel>(&mut self, to_decode: FrameBuffer) -> Result<DecodedImage<P, Self::OutputMeta>, NokhwaError>
|
||||
where
|
||||
<P as Pixel>::Subpixel: NonFloatScalarWidth
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn output_decoder_min_size(&self, resolution: Resolution, destination_format: Self::DestinationFormatHint) -> usize {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn buffer_takes_destination_hint(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_to_u8(filter: bool) -> u8 {
|
||||
if filter {
|
||||
1_u8
|
||||
} else {
|
||||
0_u8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn filter_to_u16(filter: bool) -> u16 {
|
||||
if filter {
|
||||
1_u16
|
||||
} else {
|
||||
0_u16
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LumaConfig {
|
||||
pub mode: ConvertMode,
|
||||
pub scaling_functions: ScalingFunctions,
|
||||
pub channel_filters: ChannelFilters,
|
||||
pub format: FrameFormat,
|
||||
pub custom_frame_format_map: Option<HashMap<CustomFrameFormat, FrameFormat>>
|
||||
}
|
||||
|
||||
impl TryFrom<FrameFormat> for LumaConfig {
|
||||
type Error = NokhwaError;
|
||||
|
||||
fn try_from(value: FrameFormat) -> Result<Self, Self::Error> {
|
||||
if !FrameFormat::BRIGHTNESS_LUMA.contains(&value) {
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(value))
|
||||
}
|
||||
|
||||
Ok(LumaConfig {
|
||||
mode: ConvertMode::default(),
|
||||
scaling_functions: ScalingFunctions::default(),
|
||||
channel_filters: ChannelFilters::default(),
|
||||
format: value,
|
||||
custom_frame_format_map: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ConvertMode {
|
||||
Scaled,
|
||||
Clipped
|
||||
}
|
||||
|
||||
impl Default for ConvertMode {
|
||||
fn default() -> Self {
|
||||
ConvertMode::Scaled
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ScalingFunctions {
|
||||
pub scale_up_u8_to_u16: Box<dyn FnMut(u8, u32) -> u16>,
|
||||
pub scale_down_u8_to_u16: Box<dyn FnMut(u16, u32) -> u8>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct ChannelFilters {
|
||||
pub red: bool,
|
||||
pub green: bool,
|
||||
pub blue: bool,
|
||||
pub alpha: bool,
|
||||
}
|
||||
|
||||
impl Default for ChannelFilters {
|
||||
fn default() -> Self {
|
||||
ChannelFilters {
|
||||
red: true,
|
||||
green: true,
|
||||
blue: true,
|
||||
alpha: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum LumaDestination {
|
||||
Luma8,
|
||||
LumaA8,
|
||||
Rgb8,
|
||||
Rgba8,
|
||||
Rgb16,
|
||||
Rgba16,
|
||||
}
|
||||
|
||||
struct ConstIter<T> where T: Copy + Clone + Debug + Default + Eq + Ord + PartialEq + PartialOrd {
|
||||
pub val: T
|
||||
}
|
||||
|
||||
impl<T> Iterator for ConstIter<T> where T: Copy + Clone + Debug + Default + Eq + Ord + PartialEq + PartialOrd {
|
||||
type Item = T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(self.val)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for ConstIter<T> where T: Copy + Clone + Debug + Default + Eq + Ord + PartialEq + PartialOrd {
|
||||
type Item = T;
|
||||
type IntoIter = ConstIter<T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use bytemuck::{cast_slice, cast_slice_mut, try_cast_slice_mut};
|
||||
|
||||
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive};
|
||||
use nokhwa_core::error::NokhwaError;
|
||||
use nokhwa_core::frame_buffer::FrameBuffer;
|
||||
use nokhwa_core::frame_format::FrameFormat;
|
||||
use nokhwa_core::frame_format::{CustomFrameFormat, FrameFormat};
|
||||
use nokhwa_core::image::{DecodedImage, NonFloatScalarWidth};
|
||||
use nokhwa_core::types::{CameraFormat, Resolution};
|
||||
use yuv::{
|
||||
@@ -45,6 +46,15 @@ impl Decoder for YUVDecoder {
|
||||
if !FrameFormat::YCBCR.contains(&config.yuv_type) {
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(config.yuv_type));
|
||||
}
|
||||
|
||||
if let Some(custom_map) = &config.custom_frame_format_map {
|
||||
if let Some((src, dest)) = custom_map.iter().find(|(_, value)| {
|
||||
FrameFormat::YCBCR.contains(value)
|
||||
}) {
|
||||
return Err(NokhwaError::DecoderUnsupportedCustomFrameFormatDestination(*src, *dest))
|
||||
}
|
||||
}
|
||||
|
||||
self.config = config;
|
||||
Ok(())
|
||||
}
|
||||
@@ -60,13 +70,24 @@ impl Decoder for YUVDecoder {
|
||||
None => return Err(NokhwaError::DecoderDestinationHintRequired),
|
||||
};
|
||||
|
||||
|
||||
let yuv_format = self.config().custom_frame_format_map.as_ref().map(|m| {
|
||||
match self.config.yuv_type {
|
||||
FrameFormat::Custom(cfmt) => {
|
||||
m.get(&cfmt).copied()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}).flatten().unwrap_or(self.config.yuv_type);
|
||||
|
||||
|
||||
let buffer = buffer.as_mut();
|
||||
if buffer.len() < self.output_decoder_min_size(self.config.resolution, destination_format) {
|
||||
return Err(NokhwaError::DecoderInvalidBuffer("Too small!".to_string()));
|
||||
}
|
||||
|
||||
let stride = figure_out_stride(self.config.yuv_type).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))?;
|
||||
let byte_width = figure_out_byte_width(self.config.yuv_type).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))?;
|
||||
let stride = figure_out_stride(yuv_format).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))?;
|
||||
let byte_width = figure_out_byte_width(yuv_format).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))?;
|
||||
|
||||
|
||||
let stride_3px = 3 *
|
||||
@@ -79,7 +100,7 @@ impl Decoder for YUVDecoder {
|
||||
let decode_status = match stride {
|
||||
Stride::Packed(stride) => {
|
||||
let image = prepare_to_packed_image(&to_decode, self.config.resolution, byte_width, stride);
|
||||
match self.config.yuv_type {
|
||||
match yuv_format {
|
||||
FrameFormat::Ayuv_32 => {
|
||||
match destination_format {
|
||||
YUVDestination::Rgb8 => Some(ayuv_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.premultiply_alpha)),
|
||||
@@ -129,16 +150,16 @@ impl Decoder for YUVDecoder {
|
||||
}
|
||||
_ => {
|
||||
if FrameFormat::YCBCR_PACKED.contains(&self.config.yuv_type) {
|
||||
return Err(NokhwaError::NotImplementedError("etto blehhh!".to_string()))
|
||||
return Err(NokhwaError::NotImplementedError("etto blehhh! ()".to_string()))
|
||||
}
|
||||
// shouldnt happen
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))
|
||||
}
|
||||
}
|
||||
}
|
||||
Stride::Semi(y_stride, uv_stride) => {
|
||||
let image = prepare_to_semi_planar_image(&to_decode, self.config.resolution, byte_width, y_stride, uv_stride);
|
||||
match self.config.yuv_type {
|
||||
match yuv_format {
|
||||
FrameFormat::NV24 => {
|
||||
match destination_format {
|
||||
YUVDestination::Rgb8 => Some(yuv_nv24_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
|
||||
@@ -213,17 +234,17 @@ impl Decoder for YUVDecoder {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if FrameFormat::YCBCR_SEMI.contains(&self.config.yuv_type) {
|
||||
if FrameFormat::YCBCR_SEMI.contains(&yuv_format) {
|
||||
return Err(NokhwaError::NotImplementedError("etto blehhh!".to_string()))
|
||||
}
|
||||
// shouldnt happen
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))
|
||||
}
|
||||
}
|
||||
}
|
||||
Stride::Planar(y_stride, u_stride, v_stride, line_ratio) => {
|
||||
let image = prepare_to_planar_image(&to_decode, self.config.resolution, byte_width, y_stride, u_stride, v_stride, line_ratio);
|
||||
match self.config.yuv_type {
|
||||
match yuv_format {
|
||||
FrameFormat::Yuv_4_2_0 => {
|
||||
match destination_format {
|
||||
YUVDestination::Rgb8 => Some(yuv420_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
|
||||
@@ -234,11 +255,11 @@ impl Decoder for YUVDecoder {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if FrameFormat::YCBCR_PLANAR.contains(&self.config.yuv_type) {
|
||||
if FrameFormat::YCBCR_PLANAR.contains(&yuv_format) {
|
||||
return Err(NokhwaError::NotImplementedError("etto blehhh!".to_string()))
|
||||
}
|
||||
// shouldnt happen
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))
|
||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +268,7 @@ impl Decoder for YUVDecoder {
|
||||
Some(Ok(_)) => Ok(()),
|
||||
Some(Err(why)) => Err(NokhwaError::Decoder(why.to_string())),
|
||||
None => Err(NokhwaError::DecoderUnsupportedFrameFormat(
|
||||
self.config.yuv_type,
|
||||
yuv_format,
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -362,7 +383,7 @@ impl YUVDestination {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct YUVConfig {
|
||||
pub resolution: Resolution,
|
||||
pub yuv_type: FrameFormat,
|
||||
@@ -370,6 +391,7 @@ pub struct YUVConfig {
|
||||
pub matrix: YuvStandardMatrix,
|
||||
pub mode: YuvConversionMode,
|
||||
pub premultiply_alpha: bool,
|
||||
pub custom_frame_format_map: Option<HashMap<CustomFrameFormat, FrameFormat>>,
|
||||
}
|
||||
|
||||
impl TryFrom<CameraFormat> for YUVConfig {
|
||||
@@ -386,6 +408,7 @@ impl TryFrom<CameraFormat> for YUVConfig {
|
||||
matrix: YuvStandardMatrix::Bt601,
|
||||
mode: YuvConversionMode::Balanced,
|
||||
premultiply_alpha: false,
|
||||
custom_frame_format_map: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -559,7 +582,7 @@ fn convert_bi_planar_image_to_u16(
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::yuv::{YUVConfig, YUVDecoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageBuffer, ImageFormat, ImageReader, Pixel, PixelWithColorType, Rgb, Rgba};
|
||||
use image::{DynamicImage, EncodableLayout, ImageBuffer, ImageFormat, Pixel, PixelWithColorType, Rgb, Rgba};
|
||||
use nokhwa_core::decoder::Decoder;
|
||||
use nokhwa_core::frame_buffer::FrameBuffer;
|
||||
use nokhwa_core::frame_format::FrameFormat;
|
||||
@@ -567,7 +590,7 @@ mod test {
|
||||
use nokhwa_core::types::Resolution;
|
||||
use std::borrow::Cow;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Read};
|
||||
use std::io::{BufReader, Read};
|
||||
use yuv::{YuvConversionMode, YuvRange, YuvStandardMatrix};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -593,6 +616,7 @@ where
|
||||
matrix: YuvStandardMatrix::Bt601,
|
||||
mode: YuvConversionMode::Balanced,
|
||||
premultiply_alpha: false,
|
||||
custom_frame_format_map: None,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 5.1 MiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 459 KiB |
|
After Width: | Height: | Size: 412 KiB |
|
After Width: | Height: | Size: 474 KiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 456 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 20 MiB |
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 18 MiB |
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "nokhwa-iter-extensions"
|
||||
version = "0.1.0"
|
||||
authors = ["l1npengtul <l1npengtul@protonmail.com>"]
|
||||
edition = "2024"
|
||||
description = "Core type definitions for nokhwa"
|
||||
keywords = ["iter", "iterator", "interweave", "duplicate"]
|
||||
categories = ["algorithms", "rust-patterns", "no-std", "no-std::no-alloc"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/l1npengtul/nokhwa"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,138 @@
|
||||
use core::iter::FusedIterator;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
enum DuplicateConstState {
|
||||
#[default]
|
||||
Started,
|
||||
EmitDupe,
|
||||
EmitReal,
|
||||
Finished
|
||||
}
|
||||
|
||||
/// Duplicates the items in the iterator `MULTIPLIER` times.
|
||||
///
|
||||
/// 0 acts as 1.
|
||||
///
|
||||
/// This iterator is _fused_.
|
||||
pub struct DuplicateConst<I, const MULTIPLIER: usize> where
|
||||
I: Iterator,
|
||||
<I as Iterator>::Item: Clone {
|
||||
iter: I,
|
||||
last_iter_item: Option<I::Item>,
|
||||
running_count: usize,
|
||||
state: DuplicateConstState,
|
||||
}
|
||||
|
||||
impl<I, const MULTIPLIER: usize> DuplicateConst<I, MULTIPLIER> where I: Iterator,
|
||||
<I as Iterator>::Item: Clone {
|
||||
pub fn new(iter: I) -> DuplicateConst<I, MULTIPLIER> {
|
||||
DuplicateConst {
|
||||
iter,
|
||||
last_iter_item: None,
|
||||
running_count: MULTIPLIER.saturating_sub(1),
|
||||
state: DuplicateConstState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const MULTIPLIER: usize> Iterator for DuplicateConst<I, MULTIPLIER> where
|
||||
I: Iterator,
|
||||
<I as Iterator>::Item: Clone
|
||||
{
|
||||
type Item = I::Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.state {
|
||||
DuplicateConstState::Started => {
|
||||
match self.iter.next() {
|
||||
Some(i) => {
|
||||
if MULTIPLIER <= 1 {
|
||||
self.state = DuplicateConstState::EmitReal;
|
||||
} else {
|
||||
self.state = DuplicateConstState::EmitDupe;
|
||||
}
|
||||
self.last_iter_item = Some(i.clone());
|
||||
Some(i)
|
||||
}
|
||||
None => {
|
||||
self.state = DuplicateConstState::Finished;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
DuplicateConstState::EmitDupe => {
|
||||
self.running_count = self.running_count.saturating_sub(1);
|
||||
if self.running_count <= 0 {
|
||||
self.state = DuplicateConstState::EmitReal;
|
||||
}
|
||||
self.last_iter_item.clone()
|
||||
}
|
||||
DuplicateConstState::EmitReal => {
|
||||
match self.iter.next() {
|
||||
Some(i) => {
|
||||
self.last_iter_item = Some(i.clone());
|
||||
self.running_count = MULTIPLIER.saturating_sub(1);
|
||||
if MULTIPLIER <= 1 {
|
||||
self.state = DuplicateConstState::EmitReal;
|
||||
} else {
|
||||
self.state = DuplicateConstState::EmitDupe;
|
||||
}
|
||||
Some(i)
|
||||
}
|
||||
None => {
|
||||
self.state = DuplicateConstState::Finished;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
DuplicateConstState::Finished => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (lower, upper) = self.iter.size_hint();
|
||||
let multi = if MULTIPLIER == 0 { 1 } else { MULTIPLIER + 1 };
|
||||
(lower * multi, upper.map(|u| u * multi))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const MULTIPLIER: usize> FusedIterator for DuplicateConst<I, MULTIPLIER> where I: Iterator, <I as Iterator>::Item: Clone {}
|
||||
|
||||
pub trait IterDuplicateConst: Iterator {
|
||||
fn duplicate_const<const MULTIPLIER: usize>(self) -> DuplicateConst<Self, MULTIPLIER> where Self: Sized, Self::Item: Clone {
|
||||
DuplicateConst::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ?Sized> IterDuplicateConst for I where I: Iterator {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::duplicate::IterDuplicateConst;
|
||||
|
||||
#[test]
|
||||
pub fn zero_acts_as_one() {
|
||||
let initial = vec![0, 0, 0, 0];
|
||||
let test_condition = vec![0, 0, 0, 0];
|
||||
let result = initial.into_iter().duplicate_const::<0>().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn one_is_one() {
|
||||
let initial = vec![0, 0, 0, 0];
|
||||
let test_condition = vec![0, 0, 0, 0];
|
||||
let result = initial.into_iter().duplicate_const::<1>().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn multiples() {
|
||||
let initial = vec![0, 0, 0, 0, 0];
|
||||
let test_condition = vec![0; 25];
|
||||
let result = initial.into_iter().duplicate_const::<5>().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
use core::iter::FusedIterator;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
enum InterweaveState {
|
||||
EmitFake,
|
||||
#[default]
|
||||
EmitReal,
|
||||
Finished
|
||||
}
|
||||
|
||||
/// Interweaves a value for every `PER` values of the original iterator.
|
||||
///
|
||||
/// `emit_last`: Allows you to set if a last item should be emitted even though it is not the "turn"
|
||||
/// of the "value" yet, e.g.
|
||||
///
|
||||
/// ```
|
||||
/// let initial = vec![1, 2, 1, 2, 1];
|
||||
// let test_condition = vec![1, 2, 3, 1, 2, 3, 1, 3];
|
||||
// let result = initial.iter().interweave::<2>(&3, true).cloned().collect::<Vec<i32>>();
|
||||
// assert_eq!(test_condition, result);
|
||||
/// ```
|
||||
///
|
||||
/// This iterator is _fused_.
|
||||
pub struct Interweave<I, const PER: usize> where
|
||||
I: Iterator,
|
||||
<I as Iterator>::Item: Clone {
|
||||
element: I::Item,
|
||||
iter: I,
|
||||
prev_state: InterweaveState,
|
||||
state: InterweaveState,
|
||||
count: usize,
|
||||
emit_last: bool,
|
||||
}
|
||||
|
||||
impl<I, const PER: usize> Interweave<I, PER> where I: Iterator, <I as Iterator>::Item: Clone {
|
||||
pub fn new(item: I::Item, iterator: I, emit_last: bool) -> Interweave<I, PER> {
|
||||
Interweave {
|
||||
element: item,
|
||||
iter: iterator,
|
||||
prev_state: InterweaveState::default(),
|
||||
state: InterweaveState::default(),
|
||||
count: PER,
|
||||
emit_last,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const PER: usize> Iterator for Interweave<I, PER> where I: Iterator, <I as Iterator>::Item: Clone {
|
||||
type Item = I::Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.state {
|
||||
InterweaveState::EmitFake => {
|
||||
self.count = PER;
|
||||
self.prev_state = InterweaveState::EmitFake;
|
||||
self.state = InterweaveState::EmitReal;
|
||||
Some(self.element.clone())
|
||||
}
|
||||
InterweaveState::EmitReal => {
|
||||
self.count = self.count.saturating_sub(1);
|
||||
match self.iter.next() {
|
||||
Some(i) => {
|
||||
if self.count <= 0 {
|
||||
self.state = InterweaveState::EmitFake;
|
||||
} else {
|
||||
self.state = InterweaveState::EmitReal;
|
||||
}
|
||||
self.prev_state = InterweaveState::EmitReal;
|
||||
Some(i)
|
||||
}
|
||||
None => {
|
||||
self.state = InterweaveState::Finished;
|
||||
if self.emit_last && self.prev_state != InterweaveState::EmitFake {
|
||||
Some(self.element.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
InterweaveState::Finished => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let (lower, upper) = self.iter.size_hint();
|
||||
|
||||
if PER == 1 || PER == 0 {
|
||||
return (lower * 2, upper.map(|u| u * 2))
|
||||
}
|
||||
|
||||
let last = if self.emit_last { 1 } else { 0 };
|
||||
let new_lower = lower + (lower / PER) + last;
|
||||
let new_upper = upper.map(|u| { u + (u / PER) + last });
|
||||
(new_lower, new_upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, const PER: usize> FusedIterator for Interweave<I, PER> where I: Iterator, <I as Iterator>::Item: Clone {}
|
||||
|
||||
pub trait IterInterweave: Iterator {
|
||||
fn interweave<const PER: usize>(self, item: Self::Item, emit_last: bool) -> Interweave<Self, PER> where Self: Sized, Self::Item: Clone {
|
||||
Interweave::new(item, self, emit_last)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ?Sized> IterInterweave for I where I: Iterator {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::interweave::IterInterweave;
|
||||
|
||||
#[test]
|
||||
pub fn zero_interweave() {
|
||||
let initial = vec![0, 0, 0, 0, 0];
|
||||
let test_condition = vec![0, 1, 0, 1, 0, 1, 0, 1, 0, 1];
|
||||
let result = initial.iter().interweave::<1>(&1, false).cloned().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn empty_interweave_no_last() {
|
||||
let initial: Vec<i32> = vec![];
|
||||
let test_condition: Vec<i32> = vec![];
|
||||
let result = initial.iter().interweave::<1>(&1, false).cloned().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn empty_interweave_with_last() {
|
||||
let initial: Vec<i32> = vec![];
|
||||
let test_condition: Vec<i32> = vec![1];
|
||||
let result = initial.iter().interweave::<1>(&1, true).cloned().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn interweave_every_other() {
|
||||
let initial = vec![1, 2, 1, 2, 1 ,2];
|
||||
let test_condition = vec![1, 2, 3, 1, 2, 3, 1 , 2, 3];
|
||||
let result = initial.iter().interweave::<2>(&3, true).cloned().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn interweave_every_other_not_fitting() {
|
||||
let initial = vec![1, 2, 1, 2, 1];
|
||||
let test_condition = vec![1, 2, 3, 1, 2, 3, 1, 3];
|
||||
let result = initial.iter().interweave::<2>(&3, true).cloned().collect::<Vec<i32>>();
|
||||
assert_eq!(test_condition, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#![no_std]
|
||||
#[warn(clippy::pedantic)]
|
||||
|
||||
pub mod interweave;
|
||||
pub mod duplicate;
|
||||
|
||||