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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[workspace]
|
[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]
|
[lib]
|
||||||
crate-type = ["rlib"]
|
crate-type = ["rlib"]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
use crate::frame_format::FrameFormat;
|
use crate::frame_format::{CustomFrameFormat, FrameFormat};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::types::Backends;
|
use crate::types::Backends;
|
||||||
@@ -66,6 +66,8 @@ pub enum NokhwaError {
|
|||||||
Decoder(String),
|
Decoder(String),
|
||||||
#[error("Unsupported FrameFormat: {0}")]
|
#[error("Unsupported FrameFormat: {0}")]
|
||||||
DecoderUnsupportedFrameFormat(FrameFormat),
|
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.")]
|
#[error("Unsupported pixel configuration {0} with width {1}b.")]
|
||||||
DecoderUnsupportedDestinationPixelFormat(&'static str, u32),
|
DecoderUnsupportedDestinationPixelFormat(&'static str, u32),
|
||||||
#[error("Bad decoder configuration: {0}")]
|
#[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.")]
|
#[error("You need to pass in a destination hint, it is not optional for this decoder.")]
|
||||||
DecoderDestinationHintRequired,
|
DecoderDestinationHintRequired,
|
||||||
#[error("Decoder already deinitialized. Unusable, please make a new decoder.")]
|
#[error("Decoder already deinitialized. Unusable, please make a new decoder.")]
|
||||||
DecoderAlreadyDeinitialized
|
DecoderAlreadyDeinitialized,
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// pub enum InitializeError {}
|
// 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 {
|
impl Display for FrameFormat {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{self:?}")
|
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))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum CustomFrameFormat {
|
pub enum CustomFrameFormat {
|
||||||
UUID(u128),
|
UUID(u128),
|
||||||
@@ -222,6 +231,12 @@ pub enum CustomFrameFormat {
|
|||||||
F64(OrderedFloat<f64>),
|
F64(OrderedFloat<f64>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for CustomFrameFormat {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_back_and_fourth_frame_format {
|
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) => {
|
($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"]
|
ffmpeg = ["ffmpeg-the-third"]
|
||||||
yuyv = ["dcv-color-primitives", "yuv"]
|
yuyv = ["dcv-color-primitives", "yuv"]
|
||||||
mjpeg = ["zune-jpeg", "zune-core"]
|
mjpeg = ["zune-jpeg", "zune-core"]
|
||||||
|
luma = ["itertools"]
|
||||||
#static = ["ffmpeg-the-third/static"]
|
#static = ["ffmpeg-the-third/static"]
|
||||||
async = []
|
#async = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = "1.23"
|
bytemuck = "1.23"
|
||||||
@@ -37,6 +38,10 @@ optional = true
|
|||||||
version = "0.5.0-rc2"
|
version = "0.5.0-rc2"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.itertools]
|
||||||
|
version = "0.14.0"
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dev-dependencies.image]
|
[dev-dependencies.image]
|
||||||
workspace = true
|
workspace = true
|
||||||
features = ["png", "jpeg", "avif"]
|
features = ["png", "jpeg", "avif"]
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ pub mod ffmpeg;
|
|||||||
pub mod mjpeg;
|
pub mod mjpeg;
|
||||||
#[cfg(feature = "yuyv")]
|
#[cfg(feature = "yuyv")]
|
||||||
pub mod yuv;
|
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 bytemuck::{cast_slice, cast_slice_mut, try_cast_slice_mut};
|
||||||
|
|
||||||
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive};
|
use nokhwa_core::decoder::{Decoder, ImageBuffer, Pixel, Primitive};
|
||||||
use nokhwa_core::error::NokhwaError;
|
use nokhwa_core::error::NokhwaError;
|
||||||
use nokhwa_core::frame_buffer::FrameBuffer;
|
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::image::{DecodedImage, NonFloatScalarWidth};
|
||||||
use nokhwa_core::types::{CameraFormat, Resolution};
|
use nokhwa_core::types::{CameraFormat, Resolution};
|
||||||
use yuv::{
|
use yuv::{
|
||||||
@@ -45,6 +46,15 @@ impl Decoder for YUVDecoder {
|
|||||||
if !FrameFormat::YCBCR.contains(&config.yuv_type) {
|
if !FrameFormat::YCBCR.contains(&config.yuv_type) {
|
||||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(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;
|
self.config = config;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -60,13 +70,24 @@ impl Decoder for YUVDecoder {
|
|||||||
None => return Err(NokhwaError::DecoderDestinationHintRequired),
|
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();
|
let buffer = buffer.as_mut();
|
||||||
if buffer.len() < self.output_decoder_min_size(self.config.resolution, destination_format) {
|
if buffer.len() < self.output_decoder_min_size(self.config.resolution, destination_format) {
|
||||||
return Err(NokhwaError::DecoderInvalidBuffer("Too small!".to_string()));
|
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 stride = figure_out_stride(yuv_format).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))?;
|
||||||
let byte_width = figure_out_byte_width(self.config.yuv_type).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))?;
|
let byte_width = figure_out_byte_width(yuv_format).ok_or(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))?;
|
||||||
|
|
||||||
|
|
||||||
let stride_3px = 3 *
|
let stride_3px = 3 *
|
||||||
@@ -79,7 +100,7 @@ impl Decoder for YUVDecoder {
|
|||||||
let decode_status = match stride {
|
let decode_status = match stride {
|
||||||
Stride::Packed(stride) => {
|
Stride::Packed(stride) => {
|
||||||
let image = prepare_to_packed_image(&to_decode, self.config.resolution, byte_width, 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 => {
|
FrameFormat::Ayuv_32 => {
|
||||||
match destination_format {
|
match destination_format {
|
||||||
YUVDestination::Rgb8 => Some(ayuv_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.premultiply_alpha)),
|
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) {
|
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
|
// shouldnt happen
|
||||||
return Err(NokhwaError::DecoderUnsupportedFrameFormat(self.config.yuv_type))
|
return Err(NokhwaError::DecoderUnsupportedFrameFormat(yuv_format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stride::Semi(y_stride, uv_stride) => {
|
Stride::Semi(y_stride, uv_stride) => {
|
||||||
let image = prepare_to_semi_planar_image(&to_decode, self.config.resolution, byte_width, 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 => {
|
FrameFormat::NV24 => {
|
||||||
match destination_format {
|
match destination_format {
|
||||||
YUVDestination::Rgb8 => Some(yuv_nv24_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix, self.config.mode)),
|
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()))
|
return Err(NokhwaError::NotImplementedError("etto blehhh!".to_string()))
|
||||||
}
|
}
|
||||||
// shouldnt happen
|
// 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) => {
|
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);
|
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 => {
|
FrameFormat::Yuv_4_2_0 => {
|
||||||
match destination_format {
|
match destination_format {
|
||||||
YUVDestination::Rgb8 => Some(yuv420_to_rgb(&image, buffer, stride_3px, self.config.range, self.config.matrix)),
|
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()))
|
return Err(NokhwaError::NotImplementedError("etto blehhh!".to_string()))
|
||||||
}
|
}
|
||||||
// shouldnt happen
|
// 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(Ok(_)) => Ok(()),
|
||||||
Some(Err(why)) => Err(NokhwaError::Decoder(why.to_string())),
|
Some(Err(why)) => Err(NokhwaError::Decoder(why.to_string())),
|
||||||
None => Err(NokhwaError::DecoderUnsupportedFrameFormat(
|
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 struct YUVConfig {
|
||||||
pub resolution: Resolution,
|
pub resolution: Resolution,
|
||||||
pub yuv_type: FrameFormat,
|
pub yuv_type: FrameFormat,
|
||||||
@@ -370,6 +391,7 @@ pub struct YUVConfig {
|
|||||||
pub matrix: YuvStandardMatrix,
|
pub matrix: YuvStandardMatrix,
|
||||||
pub mode: YuvConversionMode,
|
pub mode: YuvConversionMode,
|
||||||
pub premultiply_alpha: bool,
|
pub premultiply_alpha: bool,
|
||||||
|
pub custom_frame_format_map: Option<HashMap<CustomFrameFormat, FrameFormat>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<CameraFormat> for YUVConfig {
|
impl TryFrom<CameraFormat> for YUVConfig {
|
||||||
@@ -386,6 +408,7 @@ impl TryFrom<CameraFormat> for YUVConfig {
|
|||||||
matrix: YuvStandardMatrix::Bt601,
|
matrix: YuvStandardMatrix::Bt601,
|
||||||
mode: YuvConversionMode::Balanced,
|
mode: YuvConversionMode::Balanced,
|
||||||
premultiply_alpha: false,
|
premultiply_alpha: false,
|
||||||
|
custom_frame_format_map: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,7 +582,7 @@ fn convert_bi_planar_image_to_u16(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::yuv::{YUVConfig, YUVDecoder};
|
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::decoder::Decoder;
|
||||||
use nokhwa_core::frame_buffer::FrameBuffer;
|
use nokhwa_core::frame_buffer::FrameBuffer;
|
||||||
use nokhwa_core::frame_format::FrameFormat;
|
use nokhwa_core::frame_format::FrameFormat;
|
||||||
@@ -567,7 +590,7 @@ mod test {
|
|||||||
use nokhwa_core::types::Resolution;
|
use nokhwa_core::types::Resolution;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufReader, BufWriter, Read};
|
use std::io::{BufReader, Read};
|
||||||
use yuv::{YuvConversionMode, YuvRange, YuvStandardMatrix};
|
use yuv::{YuvConversionMode, YuvRange, YuvStandardMatrix};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -593,6 +616,7 @@ where
|
|||||||
matrix: YuvStandardMatrix::Bt601,
|
matrix: YuvStandardMatrix::Bt601,
|
||||||
mode: YuvConversionMode::Balanced,
|
mode: YuvConversionMode::Balanced,
|
||||||
premultiply_alpha: false,
|
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;
|
||||||
|
|
||||||