mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 00:35:44 +00:00
Bug 1432930: Part 1: Added imageattr parsing in rust, r=bwc
Added imageattr parsing in rust. Expanded the imageattr test cases in rust. MozReview-Commit-ID: IeMkbrbJAoe --HG-- extra : rebase_source : 05e4105983e7d735bce21de3bd4e99da754589ed
This commit is contained in:
parent
02f6b7fef7
commit
c34d1a7a92
@ -14,7 +14,7 @@ pub enum SdpSingleDirection{
|
||||
Recv = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub enum SdpAttributePayloadType {
|
||||
PayloadType(u8),
|
||||
@ -289,6 +289,54 @@ pub struct SdpAttributeFingerprint {
|
||||
pub fingerprint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub enum SdpAttributeImageAttrXYRange {
|
||||
Range(u32,u32,Option<u32>), // min, max, step
|
||||
DiscrateValues(Vec<u32>),
|
||||
Value(u32)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub enum SdpAttributeImageAttrSRange {
|
||||
Range(f32,f32), // min, max
|
||||
DiscrateValues(Vec<f32>),
|
||||
Value(f32)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub struct SdpAttributeImageAttrPRange {
|
||||
pub min: f32,
|
||||
pub max: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub struct SdpAttributeImageAttrSet {
|
||||
pub x: SdpAttributeImageAttrXYRange,
|
||||
pub y: SdpAttributeImageAttrXYRange,
|
||||
pub sar: Option<SdpAttributeImageAttrSRange>,
|
||||
pub par: Option<SdpAttributeImageAttrPRange>,
|
||||
pub q: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub enum SdpAttributeImageAttrSetList {
|
||||
Sets(Vec<SdpAttributeImageAttrSet>),
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub struct SdpAttributeImageAttr {
|
||||
pub pt: SdpAttributePayloadType,
|
||||
pub send: SdpAttributeImageAttrSetList,
|
||||
pub recv: SdpAttributeImageAttrSetList,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature="serialize", derive(Serialize))]
|
||||
pub struct SdpAttributeSctpmap {
|
||||
@ -430,7 +478,7 @@ pub enum SdpAttribute {
|
||||
IcePwd(String),
|
||||
IceUfrag(String),
|
||||
Identity(String),
|
||||
ImageAttr(String),
|
||||
ImageAttr(SdpAttributeImageAttr),
|
||||
Inactive,
|
||||
Label(String),
|
||||
MaxMessageSize(u64),
|
||||
@ -632,7 +680,7 @@ impl FromStr for SdpAttribute {
|
||||
"ice-pwd" => Ok(SdpAttribute::IcePwd(string_or_empty(val)?)),
|
||||
"ice-ufrag" => Ok(SdpAttribute::IceUfrag(string_or_empty(val)?)),
|
||||
"identity" => Ok(SdpAttribute::Identity(string_or_empty(val)?)),
|
||||
"imageattr" => Ok(SdpAttribute::ImageAttr(string_or_empty(val)?)),
|
||||
"imageattr" => parse_image_attr(val),
|
||||
"inactive" => Ok(SdpAttribute::Inactive),
|
||||
"label" => Ok(SdpAttribute::Label(string_or_empty(val)?)),
|
||||
"max-message-size" => Ok(SdpAttribute::MaxMessageSize(val.parse()?)),
|
||||
@ -1174,6 +1222,295 @@ fn parse_ice_options(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalEr
|
||||
Ok(SdpAttribute::IceOptions(to_parse.split_whitespace().map(|x| x.to_string()).collect()))
|
||||
}
|
||||
|
||||
fn parse_image_attr_xyrange(to_parse: &str) -> Result<SdpAttributeImageAttrXYRange, SdpParserInternalError> {
|
||||
if to_parse.starts_with("[") {
|
||||
if !to_parse.ends_with("]") {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's xyrange has no closing tag ']'".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
if to_parse.contains(":") {
|
||||
// Range values
|
||||
let range_tokens:Vec<&str> = to_parse[1..to_parse.len()-1].split(":").collect();
|
||||
|
||||
if range_tokens.len() == 3 {
|
||||
Ok(SdpAttributeImageAttrXYRange::Range(
|
||||
range_tokens[0].parse::<u32>()?,
|
||||
range_tokens[2].parse::<u32>()?,
|
||||
Some(range_tokens[1].parse::<u32>()?),
|
||||
))
|
||||
} else if range_tokens.len() == 2 {
|
||||
Ok(SdpAttributeImageAttrXYRange::Range(
|
||||
range_tokens[0].parse::<u32>()?,
|
||||
range_tokens[1].parse::<u32>()?,
|
||||
None
|
||||
))
|
||||
} else {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's xyrange must contain 2 or 3 fields".to_string()
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Discrete values
|
||||
let values = to_parse[1..to_parse.len()-1]
|
||||
.split(",")
|
||||
.map(|x| x.parse::<u32>())
|
||||
.collect::<Result<Vec<u32>, _>>()?;
|
||||
|
||||
if values.len() == 0 {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's discrete value list is empty".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
Ok(SdpAttributeImageAttrXYRange::DiscrateValues(values))
|
||||
}
|
||||
|
||||
} else {
|
||||
Ok(SdpAttributeImageAttrXYRange::Value(to_parse.parse::<u32>()?))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_image_attr_set(to_parse: &str) -> Result<SdpAttributeImageAttrSet, SdpParserInternalError> {
|
||||
let result_from_option = |opt, error_msg: &str| {
|
||||
match opt {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(SdpParserInternalError::Generic(error_msg.to_string()))
|
||||
}
|
||||
};
|
||||
|
||||
let mut tokens = Vec::new();
|
||||
let mut currently_in_braces = false;
|
||||
let mut current_tokens = Vec::new();
|
||||
|
||||
for token in to_parse.split(",") {
|
||||
if token.contains("[") {
|
||||
currently_in_braces = true;
|
||||
}
|
||||
if token.contains("]") {
|
||||
currently_in_braces = false;
|
||||
}
|
||||
|
||||
current_tokens.push(token.to_string());
|
||||
|
||||
if !currently_in_braces {
|
||||
tokens.push(current_tokens.join(","));
|
||||
current_tokens = Vec::new();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let mut tokens = tokens.into_iter();
|
||||
|
||||
let x_token = result_from_option(tokens.next(),"imageattr set is missing the 'x=' token")?;
|
||||
if !x_token.starts_with("x=") {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"The first token in an imageattr set must begin with 'x='".to_string()
|
||||
))
|
||||
}
|
||||
let x = parse_image_attr_xyrange(&x_token[2..])?;
|
||||
|
||||
|
||||
let y_token = result_from_option(tokens.next(),"imageattr set is missing the 'y=' token")?;
|
||||
if !y_token.starts_with("y=") {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"The second token in an imageattr set must begin with 'y='".to_string()
|
||||
))
|
||||
}
|
||||
let y = parse_image_attr_xyrange(&y_token[2..])?;
|
||||
|
||||
let mut sar = None;
|
||||
let mut par = None;
|
||||
let mut q = None;
|
||||
|
||||
let parse_ps_range = |x: &str| -> Result<(f32, f32), SdpParserInternalError> {
|
||||
let range_tokens = &x[1..x.len()-1];
|
||||
|
||||
let parse_prange_value = |maybe_next: Option<&str>| -> Result<f32, SdpParserInternalError> {
|
||||
match maybe_next {
|
||||
Some(x) => Ok(x.parse::<f32>()?),
|
||||
None => Err(SdpParserInternalError::Generic(
|
||||
"imageattr's par and sar ranges must have two components"
|
||||
.to_string()))
|
||||
}
|
||||
};
|
||||
|
||||
let mut minmax_pair = range_tokens.split("-");
|
||||
|
||||
let min = parse_prange_value(minmax_pair.next())?;
|
||||
let max = parse_prange_value(minmax_pair.next())?;
|
||||
|
||||
if min >= max {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"In imageattr's par and sar ranges, first must be < than the second".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
Ok((min,max))
|
||||
};
|
||||
|
||||
while let Some(current_token) = tokens.next() {
|
||||
if current_token.starts_with("sar=") {
|
||||
let value_token = ¤t_token[4..];
|
||||
if value_token.starts_with("[") {
|
||||
if !value_token.ends_with("]") {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's sar value is missing closing tag ']'".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
if value_token.contains("-") {
|
||||
// Range
|
||||
let range = parse_ps_range(&value_token)?;
|
||||
sar = Some(SdpAttributeImageAttrSRange::Range(range.0,range.1))
|
||||
} else if value_token.contains(",") {
|
||||
// Discrete values
|
||||
let values = value_token[1..value_token.len()-1]
|
||||
.split(",")
|
||||
.map(|x| x.parse::<f32>())
|
||||
.collect::<Result<Vec<f32>, _>>()?;
|
||||
|
||||
if values.len() == 0 {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's sar discrete value list is empty".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
// Check that all the values are ascending
|
||||
let mut last_value = 0.0;
|
||||
for value in &values {
|
||||
if last_value >= *value {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's sar discrete value list must contain ascending values"
|
||||
.to_string()
|
||||
))
|
||||
}
|
||||
last_value = *value;
|
||||
}
|
||||
sar = Some(SdpAttributeImageAttrSRange::DiscrateValues(values))
|
||||
}
|
||||
} else {
|
||||
sar = Some(SdpAttributeImageAttrSRange::Value(value_token.parse::<f32>()?))
|
||||
}
|
||||
} else if current_token.starts_with("par=") {
|
||||
let braced_value_token = ¤t_token[4..];
|
||||
if !braced_value_token.starts_with("[") || !braced_value_token.ends_with("]") {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr's par value is missing braces".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
let range = parse_ps_range(&braced_value_token)?;
|
||||
par = Some(SdpAttributeImageAttrPRange {
|
||||
min: range.0,
|
||||
max: range.1,
|
||||
})
|
||||
} else if current_token.starts_with("q=") {
|
||||
q = Some(current_token[2..].parse::<f32>()?);
|
||||
} else {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
format!("imageattr set contains unknown value token '{:?}'", current_token)
|
||||
.to_string()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SdpAttributeImageAttrSet {
|
||||
x,
|
||||
y,
|
||||
sar,
|
||||
par,
|
||||
q
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_image_attr(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
|
||||
let result_from_option = |opt, error_msg: &str| {
|
||||
match opt {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(SdpParserInternalError::Generic(error_msg.to_string()))
|
||||
}
|
||||
};
|
||||
let mut tokens = to_parse.split(' ').peekable();
|
||||
|
||||
let pt = parse_payload_type(result_from_option(tokens.next(),
|
||||
"imageattr's requires a payload token")?)?;
|
||||
let first_direction = parse_single_direction(result_from_option(tokens.next(), "")?)?;
|
||||
|
||||
let parse_set = |set_token:&str| -> Result<SdpAttributeImageAttrSet, SdpParserInternalError> {
|
||||
Ok(parse_image_attr_set(&set_token[1..set_token.len()-1])?)
|
||||
};
|
||||
|
||||
let first_set_list = match result_from_option(tokens.next(),
|
||||
"imageattr must have at least one parameter set")? {
|
||||
"*" => SdpAttributeImageAttrSetList::Wildcard,
|
||||
x @ _ => {
|
||||
let mut sets = vec![parse_set(x)?];
|
||||
while let Some(set_str) = tokens.clone().peek() {
|
||||
if set_str.starts_with("[") && set_str.ends_with("]") {
|
||||
sets.push(parse_set(tokens.next().unwrap())?);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SdpAttributeImageAttrSetList::Sets(sets)
|
||||
}
|
||||
};
|
||||
|
||||
let mut second_set_list = SdpAttributeImageAttrSetList::Sets(Vec::new());
|
||||
|
||||
// Check if there is a second direction defined
|
||||
if let Some(x) = tokens.next() {
|
||||
if parse_single_direction(x)? == first_direction {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"image-attr's second direction token must be different from the first one"
|
||||
.to_string()
|
||||
))
|
||||
}
|
||||
|
||||
second_set_list = match result_from_option(tokens.next(),
|
||||
"imageattr must have a parameter set after a direction token")? {
|
||||
"*" => SdpAttributeImageAttrSetList::Wildcard,
|
||||
x @ _ => {
|
||||
let mut sets = vec![parse_set(x)?];
|
||||
while let Some(set_str) = tokens.clone().peek() {
|
||||
if set_str.starts_with("[") && set_str.ends_with("]") {
|
||||
sets.push(parse_set(tokens.next().unwrap())?);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SdpAttributeImageAttrSetList::Sets(sets)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if let Some(_) = tokens.next() {
|
||||
return Err(SdpParserInternalError::Generic(
|
||||
"imageattr must not contain any token after the second set list".to_string()
|
||||
))
|
||||
}
|
||||
|
||||
Ok(SdpAttribute::ImageAttr(match first_direction {
|
||||
SdpSingleDirection::Send =>
|
||||
SdpAttributeImageAttr {
|
||||
pt,
|
||||
send: first_set_list,
|
||||
recv: second_set_list,
|
||||
},
|
||||
SdpSingleDirection::Recv =>
|
||||
SdpAttributeImageAttr {
|
||||
pt,
|
||||
send: second_set_list,
|
||||
recv: first_set_list,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_msid(to_parse: &str) -> Result<SdpAttribute, SdpParserInternalError> {
|
||||
let mut tokens = to_parse.split_whitespace();
|
||||
let id = match tokens.next() {
|
||||
@ -1884,16 +2221,84 @@ fn test_parse_attribute_identity() {
|
||||
|
||||
#[test]
|
||||
fn test_parse_attribute_imageattr() {
|
||||
let check_parse = |x| -> SdpAttributeImageAttr {
|
||||
if let Ok(SdpType::Attribute(SdpAttribute::ImageAttr(x))) = parse_attribute(x) {
|
||||
x
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
assert!(parse_attribute("imageattr:120 send * recv *").is_ok());
|
||||
assert!(
|
||||
parse_attribute(
|
||||
"imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]"
|
||||
).is_ok()
|
||||
);
|
||||
assert!(parse_attribute("imageattr:99 send [x=320,y=240] recv [x=320,y=240]").is_ok());
|
||||
assert!(parse_attribute("imageattr:97 send [x=800,y=640,sar=1.1,q=0.6] [x=480,y=320] recv [x=330,y=250]").is_ok());
|
||||
assert!(parse_attribute("imageattr:97 recv [x=800,y=640,sar=1.1] send [x=330,y=250]").is_ok());
|
||||
assert!(parse_attribute("imageattr:97 send [x=[480:16:800],y=[320:16:640],par=[1.2-1.3],q=0.6] [x=[176:8:208],y=[144:8:176],par=[1.2-1.3]] recv *").is_ok());
|
||||
|
||||
let mut imageattr = check_parse("imageattr:* recv [x=800,y=[50,80,30],sar=1.1] send [x=330,y=250,sar=[1.1,1.3,1.9],q=0.1]");
|
||||
assert_eq!(imageattr.pt, SdpAttributePayloadType::Wildcard);
|
||||
match imageattr.recv {
|
||||
SdpAttributeImageAttrSetList::Sets(sets) => {
|
||||
assert_eq!(sets.len(), 1);
|
||||
|
||||
let set = &sets[0];
|
||||
assert_eq!(set.x, SdpAttributeImageAttrXYRange::Value(800));
|
||||
assert_eq!(set.y, SdpAttributeImageAttrXYRange::DiscrateValues(vec![50,80,30]));
|
||||
assert_eq!(set.par, None);
|
||||
assert_eq!(set.sar, Some(SdpAttributeImageAttrSRange::Value(1.1)));
|
||||
assert_eq!(set.q, None);
|
||||
|
||||
},
|
||||
_ => { unreachable!(); }
|
||||
}
|
||||
match imageattr.send {
|
||||
SdpAttributeImageAttrSetList::Sets(sets) => {
|
||||
assert_eq!(sets.len(), 1);
|
||||
|
||||
let set = &sets[0];
|
||||
assert_eq!(set.x, SdpAttributeImageAttrXYRange::Value(330));
|
||||
assert_eq!(set.y, SdpAttributeImageAttrXYRange::Value(250));
|
||||
assert_eq!(set.par, None);
|
||||
assert_eq!(set.sar, Some(SdpAttributeImageAttrSRange::DiscrateValues(vec![1.1,1.3,1.9])));
|
||||
assert_eq!(set.q, Some(0.1));
|
||||
},
|
||||
_ => { unreachable!(); }
|
||||
}
|
||||
|
||||
imageattr = check_parse("imageattr:97 send [x=[480:16:800],y=[100,200,300],par=[1.2-1.3],q=0.6] [x=1080,y=[144:176],sar=[0.5-0.7]] recv *");
|
||||
assert_eq!(imageattr.pt, SdpAttributePayloadType::PayloadType(97));
|
||||
match imageattr.send {
|
||||
SdpAttributeImageAttrSetList::Sets(sets) => {
|
||||
assert_eq!(sets.len(), 2);
|
||||
|
||||
let first_set = &sets[0];
|
||||
assert_eq!(first_set.x, SdpAttributeImageAttrXYRange::Range(480, 800, Some(16)));
|
||||
assert_eq!(first_set.y, SdpAttributeImageAttrXYRange::DiscrateValues(vec![100, 200, 300]));
|
||||
assert_eq!(first_set.par, Some(SdpAttributeImageAttrPRange {
|
||||
min: 1.2,
|
||||
max: 1.3
|
||||
}));
|
||||
assert_eq!(first_set.sar, None);
|
||||
assert_eq!(first_set.q, Some(0.6));
|
||||
|
||||
let second_set = &sets[1];
|
||||
assert_eq!(second_set.x, SdpAttributeImageAttrXYRange::Value(1080));
|
||||
assert_eq!(second_set.y, SdpAttributeImageAttrXYRange::Range(144, 176, None));
|
||||
assert_eq!(second_set.par, None);
|
||||
assert_eq!(second_set.sar, Some(SdpAttributeImageAttrSRange::Range(0.5, 0.7)));
|
||||
assert_eq!(second_set.q, None);
|
||||
},
|
||||
_ => { unreachable!(); }
|
||||
}
|
||||
assert_eq!(imageattr.recv, SdpAttributeImageAttrSetList::Wildcard);
|
||||
|
||||
assert!(parse_attribute("imageattr:99 send [x=320,y=240]").is_ok());
|
||||
assert!(parse_attribute("imageattr:100 recv [x=320,y=240]").is_ok());
|
||||
|
||||
assert!(parse_attribute("imageattr:").is_err());
|
||||
assert!(parse_attribute("imageattr:100").is_err());
|
||||
assert!(parse_attribute("imageattr:120 send * recv * send *").is_err());
|
||||
assert!(parse_attribute("imageattr:97 send [x=800,y=640,sar=1.1] send [x=330,y=250]").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -744,19 +744,38 @@ pub unsafe extern "C" fn sdp_get_rtcpfbs(attributes: *const Vec<SdpAttribute>, r
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RustSdpAttributeImageAttr {
|
||||
pub pt: u32,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a SdpAttributeImageAttr> for RustSdpAttributeImageAttr {
|
||||
fn from(other: &SdpAttributeImageAttr) -> Self {
|
||||
RustSdpAttributeImageAttr {
|
||||
pt: match other.pt {
|
||||
SdpAttributePayloadType::Wildcard => u32::max_value(),
|
||||
SdpAttributePayloadType::PayloadType(x) => x as u32,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sdp_get_imageattr_count(attributes: *const Vec<SdpAttribute>) -> size_t {
|
||||
count_attribute((*attributes).as_slice(), RustSdpAttributeType::ImageAttr)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sdp_get_imageattrs(attributes: *const Vec<SdpAttribute>, ret_size: size_t, ret_groups: *mut StringView) {
|
||||
let attrs: Vec<_> = (*attributes).iter().filter_map(|x| if let SdpAttribute::ImageAttr(ref string) = *x {
|
||||
Some(StringView::from(string.as_str()))
|
||||
pub unsafe extern "C" fn sdp_get_imageattrs(attributes: *const Vec<SdpAttribute>, ret_size: size_t, ret_attrs: *mut RustSdpAttributeImageAttr) {
|
||||
let attrs: Vec<_> = (*attributes).iter().filter_map(|x| if let SdpAttribute::ImageAttr(ref data) = *x {
|
||||
Some(RustSdpAttributeImageAttr::from(data))
|
||||
} else {
|
||||
None
|
||||
}).collect();
|
||||
let imageattrs = slice::from_raw_parts_mut(ret_groups, ret_size);
|
||||
let imageattrs = slice::from_raw_parts_mut(ret_attrs, ret_size);
|
||||
imageattrs.copy_from_slice(attrs.as_slice());
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user