nokhwa-iter helper crate

This commit is contained in:
l1npengtul
2025-09-12 20:01:01 +09:00
parent 97048c2d49
commit f6594f7852
41 changed files with 613 additions and 21 deletions
+1 -1
View File
@@ -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"]
+4 -2
View File
@@ -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 {}
+16 -1
View File
@@ -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) => {
+6 -1
View File
@@ -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"]
+2
View File
@@ -6,3 +6,5 @@ pub mod ffmpeg;
pub mod mjpeg;
#[cfg(feature = "yuyv")]
pub mod yuv;
#[cfg(feature = "luma")]
pub mod luma;
+228
View File
@@ -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
}
}
+40 -16
View File
@@ -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,
}
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 MiB

File diff suppressed because one or more lines are too long
+12
View File
@@ -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]
+138
View File
@@ -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)
}
}
+155
View File
@@ -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);
}
}
+6
View File
@@ -0,0 +1,6 @@
#![no_std]
#[warn(clippy::pedantic)]
pub mod interweave;
pub mod duplicate;