use std::borrow::Cow;
use crate::messages::Timestamp;
use serde::{Deserialize, Serialize};
use super::{
Compression, Endian, Image, ImageMessage, RawImageEncoding, UnknownCompressionError,
UnknownEncodingError,
};
#[derive(Debug, thiserror::Error)]
pub enum OmgidlDecodeError {
#[error(transparent)]
Cdr(#[from] cdr::Error),
#[error("timestamp out of range")]
InvalidTimestamp,
#[error(transparent)]
UnknownEncoding(#[from] UnknownEncodingError),
#[error(transparent)]
UnknownCompression(#[from] UnknownCompressionError),
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
struct OmgidlTime {
sec: u32,
nsec: u32,
}
impl TryFrom<OmgidlTime> for Timestamp {
type Error = OmgidlDecodeError;
fn try_from(value: OmgidlTime) -> Result<Self, Self::Error> {
Timestamp::new_checked(value.sec, value.nsec).ok_or(OmgidlDecodeError::InvalidTimestamp)
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct OmgidlRawImage<'a> {
timestamp: OmgidlTime,
frame_id: Cow<'a, str>,
width: u32,
height: u32,
encoding: Cow<'a, str>,
step: u32,
data: Cow<'a, [u8]>,
}
impl<'a> OmgidlRawImage<'a> {
pub fn decode(data: &'a [u8]) -> Result<Self, OmgidlDecodeError> {
Ok(cdr::deserialize::<Self>(data)?)
}
}
impl<'a> TryFrom<OmgidlRawImage<'a>> for ImageMessage<'a> {
type Error = OmgidlDecodeError;
fn try_from(image: OmgidlRawImage<'a>) -> std::result::Result<Self, Self::Error> {
let encoding = RawImageEncoding::parse_endian(&image.encoding, Endian::Little)?;
Ok(Self {
timestamp: Some(image.timestamp.try_into()?),
frame_id: image.frame_id.into(),
image: Image::Raw(super::RawImage {
encoding,
width: image.width,
height: image.height,
stride: image.step,
data: image.data,
}),
})
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct OmgidlCompressedImage<'a> {
timestamp: OmgidlTime,
frame_id: Cow<'a, str>,
data: Cow<'a, [u8]>,
format: Cow<'a, str>,
}
impl<'a> OmgidlCompressedImage<'a> {
pub fn decode(data: &'a [u8]) -> Result<Self, OmgidlDecodeError> {
Ok(cdr::deserialize::<Self>(data)?)
}
}
impl<'a> TryFrom<OmgidlCompressedImage<'a>> for ImageMessage<'a> {
type Error = OmgidlDecodeError;
fn try_from(image: OmgidlCompressedImage<'a>) -> std::result::Result<Self, Self::Error> {
let compression = Compression::try_from_ros_format(&image.format)?;
Ok(ImageMessage {
timestamp: Some(image.timestamp.try_into()?),
frame_id: image.frame_id.into(),
image: Image::Compressed(super::CompressedImage {
compression,
data: image.data,
}),
})
}
}
#[cfg(test)]
mod tests {
use cdr::{CdrLe, Infinite};
use super::*;
#[test]
fn test_roundtrip_raw_image() {
let image = OmgidlRawImage {
timestamp: OmgidlTime {
sec: 100,
nsec: 200,
},
frame_id: "frame".into(),
height: 16,
width: 16,
encoding: "mono8".into(),
step: 16,
data: Cow::Owned((0..=255).collect()),
};
let encoded = cdr::serialize::<_, _, CdrLe>(&image, Infinite).unwrap();
let decoded: OmgidlRawImage = cdr::deserialize(&encoded).unwrap();
assert_eq!(image, decoded);
}
#[test]
fn test_roundtrip_compressed_image() {
let image = OmgidlCompressedImage {
timestamp: OmgidlTime {
sec: 100,
nsec: 200,
},
frame_id: "frame".into(),
data: Cow::Owned((0..=255).collect()),
format: "png".into(),
};
let encoded = cdr::serialize::<_, _, CdrLe>(&image, Infinite).unwrap();
let decoded: OmgidlCompressedImage = cdr::deserialize(&encoded).unwrap();
assert_eq!(image, decoded);
}
#[test]
#[cfg(feature = "img2yuv-png")]
fn test_compressed_image_into_message() {
let image = OmgidlCompressedImage {
timestamp: OmgidlTime { sec: 1, nsec: 2 },
frame_id: "frame".into(),
data: Cow::Owned(vec![0, 1, 2, 3]),
format: "png".into(),
};
let msg = ImageMessage::try_from(image).unwrap();
assert_eq!(msg.frame_id, "frame");
match msg.image {
Image::Compressed(image) => assert_eq!(image.compression, Compression::Png),
other => panic!("expected compressed image, got {other:?}"),
}
}
#[test]
fn test_raw_image_rejects_overflowing_timestamp() {
let image = OmgidlRawImage {
timestamp: OmgidlTime {
sec: u32::MAX,
nsec: 1_000_000_000,
},
frame_id: "frame".into(),
height: 1,
width: 1,
encoding: "mono8".into(),
step: 1,
data: Cow::Owned(vec![0]),
};
let err = ImageMessage::try_from(image).unwrap_err();
assert!(matches!(err, OmgidlDecodeError::InvalidTimestamp));
}
}