use std::fmt::{self, Debug};
use crate::buf::{Buf, Element, PackedElement};
use crate::error::{Error, ErrorKind};
use crate::dev::DevState;
pub type Scalar<T> = Buf<T, 0>;
pub type Spectrum<T> = Buf<T, 1>;
pub type Image<T> = Buf<T, 2>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(transparent)]
pub struct DevEnum(pub i16);
#[derive(Debug, Clone, PartialEq)]
pub struct DevEncoded {
pub format: Scalar<str>,
pub data: Spectrum<u8>,
}
impl DevEncoded {
#[inline]
pub fn new(format: impl Into<Scalar<str>>, data: impl Into<Spectrum<u8>>) -> Self {
Self { format: format.into(), data: data.into() }
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Void,
Bool(ValueFormat<bool>),
Octet(ValueFormat<u8>),
UShort(ValueFormat<u16>),
Short(ValueFormat<i16>),
ULong(ValueFormat<u32>),
Long(ValueFormat<i32>),
ULong64(ValueFormat<u64>),
Long64(ValueFormat<i64>),
Float(ValueFormat<f32>),
Double(ValueFormat<f64>),
Enum(ValueFormat<DevEnum>),
State(ValueFormat<DevState>),
String(ValueFormat<str>),
LongString(Spectrum<i32>, Spectrum<str>),
DoubleString(Spectrum<f64>, Spectrum<str>),
Encoded(DevEncoded),
}
pub enum ValueFormat<T: Element + ?Sized> {
Scalar(Scalar<T>),
Spectrum(Spectrum<T>),
Image(Image<T>),
}
macro_rules! impl_value_format {
( $( $variant:ident: $get:ident, $into:ident; )* ) => {
impl<T: Element + ?Sized> ValueFormat<T> {
$(
#[doc = concat!("The data, if it is a ", stringify!($get), ".")]
#[inline]
pub fn $get(&self) -> Option<&$variant<T>> {
match self {
Self::$variant(data) => Some(data),
_ => None,
}
}
#[doc = concat!("The data, if it is a ", stringify!($get), ", or this format back.")]
#[inline]
pub fn $into(self) -> Result<$variant<T>, Self> {
match self {
Self::$variant(data) => Ok(data),
format => Err(format),
}
}
)*
}
};
}
impl_value_format! {
Scalar: scalar, into_scalar;
Spectrum: spectrum, into_spectrum;
Image: image, into_image;
}
impl<T: Element + ?Sized> Clone for ValueFormat<T> {
fn clone(&self) -> Self {
match self {
Self::Scalar(data) => Self::Scalar(data.clone()),
Self::Spectrum(data) => Self::Spectrum(data.clone()),
Self::Image(data) => Self::Image(data.clone()),
}
}
}
impl<T: Element + ?Sized> Debug for ValueFormat<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Scalar(data) => data.fmt(f),
Self::Spectrum(data) => data.fmt(f),
Self::Image(data) => data.fmt(f),
}
}
}
impl<T: Element + ?Sized> PartialEq for ValueFormat<T>
where
for<'a> T::Ref<'a>: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Scalar(a), Self::Scalar(b)) => a == b,
(Self::Spectrum(a), Self::Spectrum(b)) => a == b,
(Self::Image(a), Self::Image(b)) => a == b,
_ => false,
}
}
}
pub trait TangoElement: Element {
const ATTR_KIND: AttrDataKind;
const CMD_SCALAR_KIND: Option<CommandDataKind>;
const CMD_ARRAY_KIND: Option<CommandDataKind>;
fn wrap(format: ValueFormat<Self>) -> Value;
fn unwrap(data: Value) -> Result<ValueFormat<Self>, Value>;
fn unwrap_ref(data: &Value) -> Option<&ValueFormat<Self>>;
}
macro_rules! impl_tango_element {
($($ty:ty => $variant:ident, $scalar:expr, $array:expr;)*) => { $(
impl TangoElement for $ty {
const ATTR_KIND: AttrDataKind = AttrDataKind::$variant;
const CMD_SCALAR_KIND: Option<CommandDataKind> = $scalar;
const CMD_ARRAY_KIND: Option<CommandDataKind> = $array;
#[inline]
fn wrap(format: ValueFormat<Self>) -> Value {
Value::$variant(format)
}
#[inline]
fn unwrap(data: Value) -> Result<ValueFormat<Self>, Value> {
match data {
Value::$variant(format) => Ok(format),
data => Err(data),
}
}
#[inline]
fn unwrap_ref(data: &Value) -> Option<&ValueFormat<Self>> {
match data {
Value::$variant(format) => Some(format),
_ => None,
}
}
}
)* };
}
impl_tango_element! {
bool => Bool, Some(CommandDataKind::Bool), Some(CommandDataKind::BoolArray);
u8 => Octet, Some(CommandDataKind::Octet), Some(CommandDataKind::OctetArray);
u16 => UShort, Some(CommandDataKind::UShort), Some(CommandDataKind::UShortArray);
i16 => Short, Some(CommandDataKind::Short), Some(CommandDataKind::ShortArray);
u32 => ULong, Some(CommandDataKind::ULong), Some(CommandDataKind::ULongArray);
i32 => Long, Some(CommandDataKind::Long), Some(CommandDataKind::LongArray);
u64 => ULong64, Some(CommandDataKind::ULong64), Some(CommandDataKind::ULong64Array);
i64 => Long64, Some(CommandDataKind::Long64), Some(CommandDataKind::Long64Array);
f32 => Float, Some(CommandDataKind::Float), Some(CommandDataKind::FloatArray);
f64 => Double, Some(CommandDataKind::Double), Some(CommandDataKind::DoubleArray);
DevEnum => Enum, None, None;
DevState => State, Some(CommandDataKind::State), Some(CommandDataKind::StateArray);
str => String, Some(CommandDataKind::String), Some(CommandDataKind::StringArray);
}
pub trait FromValue: Sized {
fn from_value(value: Value) -> Result<Self, Error>;
}
impl<T> FromValue for T
where
T: TryFrom<Value>,
T::Error: Into<Error>,
{
#[inline]
fn from_value(value: Value) -> Result<Self, Error> {
T::try_from(value).map_err(Into::into)
}
}
pub trait IntoValue {
fn into_value(self) -> Value;
}
impl<T: Into<Value>> IntoValue for T {
#[inline]
fn into_value(self) -> Value {
self.into()
}
}
macro_rules! impl_data_conversions {
( $( $variant:ident: $get:ident, $into:ident; )* ) => {
$(
impl<T: TangoElement + ?Sized> From<$variant<T>> for Value {
#[inline]
fn from(data: $variant<T>) -> Self {
T::wrap(ValueFormat::$variant(data))
}
}
impl<T: TangoElement + ?Sized> TryFrom<Value> for $variant<T> {
type Error = Error;
fn try_from(data: Value) -> Result<Self, Error> {
T::unwrap(data)
.and_then(|format| format.$into().map_err(T::wrap))
.map_err(|data| Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected a {:?} {}, got {data:?}", T::ATTR_KIND, stringify!($get))))
}
}
impl<'a, T: TangoElement + ?Sized> TryFrom<&'a Value> for &'a $variant<T> {
type Error = Error;
fn try_from(data: &'a Value) -> Result<Self, Error> {
T::unwrap_ref(data)
.and_then(ValueFormat::$get)
.ok_or_else(|| Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected a {:?} {}, got {data:?}", T::ATTR_KIND, stringify!($get))))
}
}
)*
};
}
impl_data_conversions! {
Scalar: scalar, into_scalar;
Spectrum: spectrum, into_spectrum;
Image: image, into_image;
}
impl<T: TangoElement + PackedElement> From<T> for Value {
#[inline]
fn from(value: T) -> Self {
Buf::<T, 0>::from(value).into()
}
}
impl<T: TangoElement + PackedElement> From<&[T]> for Value {
#[inline]
fn from(values: &[T]) -> Self {
Buf::from(values).into()
}
}
impl<T: TangoElement + PackedElement> From<Vec<T>> for Value {
#[inline]
fn from(values: Vec<T>) -> Self {
Buf::from(values).into()
}
}
impl<T: TangoElement + PackedElement> From<Box<[T]>> for Value {
#[inline]
fn from(values: Box<[T]>) -> Self {
Buf::from(values).into()
}
}
impl<T: TangoElement + PackedElement, const N: usize> From<[T; N]> for Value {
#[inline]
fn from(values: [T; N]) -> Self {
Buf::from(values).into()
}
}
impl From<&str> for Value {
#[inline]
fn from(value: &str) -> Self {
Buf::<str, 0>::from(value).into()
}
}
impl From<String> for Value {
#[inline]
fn from(value: String) -> Self {
Self::from(value.as_str())
}
}
impl<const N: usize> From<[&str; N]> for Value {
#[inline]
fn from(values: [&str; N]) -> Self {
Buf::<str, 1>::from(&values[..]).into()
}
}
impl From<&[&str]> for Value {
#[inline]
fn from(values: &[&str]) -> Self {
Buf::<str, 1>::from(values).into()
}
}
impl From<Vec<String>> for Value {
#[inline]
fn from(values: Vec<String>) -> Self {
Buf::<str, 1>::from(values.as_slice()).into()
}
}
impl From<()> for Value {
#[inline]
fn from((): ()) -> Self {
Self::Void
}
}
impl From<(Buf<i32, 1>, Buf<str, 1>)> for Value {
#[inline]
fn from((longs, strings): (Buf<i32, 1>, Buf<str, 1>)) -> Self {
Self::LongString(longs, strings)
}
}
impl From<(Buf<f64, 1>, Buf<str, 1>)> for Value {
#[inline]
fn from((doubles, strings): (Buf<f64, 1>, Buf<str, 1>)) -> Self {
Self::DoubleString(doubles, strings)
}
}
impl From<DevEncoded> for Value {
#[inline]
fn from(encoded: DevEncoded) -> Self {
Self::Encoded(encoded)
}
}
macro_rules! impl_scalar_try_from {
($($ty:ty),*) => { $(
impl TryFrom<Value> for $ty {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
Self::try_from(&data)
}
}
impl TryFrom<&Value> for $ty {
type Error = Error;
#[inline]
fn try_from(data: &Value) -> Result<Self, Error> {
<&Buf<$ty, 0>>::try_from(data).map(Buf::value)
}
}
)* };
}
impl_scalar_try_from!(bool, u8, u16, i16, u32, i32, u64, i64, f32, f64, DevEnum, DevState);
impl<T: TangoElement + PackedElement> TryFrom<Value> for Vec<T> {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
<&[T]>::try_from(&data).map(<[T]>::to_vec)
}
}
impl<T: TangoElement + PackedElement> TryFrom<Value> for Box<[T]> {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
<&[T]>::try_from(&data).map(Box::from)
}
}
impl<T: TangoElement + PackedElement, const N: usize> TryFrom<Value> for [T; N] {
type Error = Error;
fn try_from(data: Value) -> Result<Self, Error> {
let values = <&[T]>::try_from(&data)?;
values.try_into().map_err(|_| Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected a spectrum of {N} values, got {}", values.len())))
}
}
impl<'a, T: TangoElement + PackedElement> TryFrom<&'a Value> for &'a [T] {
type Error = Error;
#[inline]
fn try_from(data: &'a Value) -> Result<Self, Error> {
<&Buf<T, 1>>::try_from(data).map(Buf::as_slice)
}
}
impl TryFrom<Value> for String {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
<&str>::try_from(&data).map(str::to_string)
}
}
impl<'a> TryFrom<&'a Value> for &'a str {
type Error = Error;
#[inline]
fn try_from(data: &'a Value) -> Result<Self, Error> {
<&Buf<str, 0>>::try_from(data).map(Buf::value)
}
}
impl TryFrom<Value> for Vec<String> {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
<&Buf<str, 1>>::try_from(&data).map(|strings| strings.iter().map(str::to_string).collect())
}
}
impl TryFrom<Value> for () {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
match data {
Value::Void => Ok(()),
data => Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected a void, got {data:?}"))),
}
}
}
impl TryFrom<Value> for (Buf<i32, 1>, Buf<str, 1>) {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
match data {
Value::LongString(longs, strings) => Ok((longs, strings)),
data => Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected a long string array, got {data:?}"))),
}
}
}
impl TryFrom<Value> for (Buf<f64, 1>, Buf<str, 1>) {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
match data {
Value::DoubleString(doubles, strings) => Ok((doubles, strings)),
data => Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected a double string array, got {data:?}"))),
}
}
}
impl TryFrom<Value> for DevEncoded {
type Error = Error;
#[inline]
fn try_from(data: Value) -> Result<Self, Error> {
match data {
Value::Encoded(encoded) => Ok(encoded),
data => Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected an encoded value, got {data:?}"))),
}
}
}
impl<'a> TryFrom<&'a Value> for &'a DevEncoded {
type Error = Error;
#[inline]
fn try_from(data: &'a Value) -> Result<Self, Error> {
match data {
Value::Encoded(encoded) => Ok(encoded),
data => Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Expected an encoded value, got {data:?}"))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CommandDataKind {
Void,
Bool,
Octet,
UShort,
Short,
ULong,
Long,
ULong64,
Long64,
Float,
Double,
State,
String,
BoolArray,
OctetArray,
UShortArray,
ShortArray,
ULongArray,
LongArray,
ULong64Array,
Long64Array,
FloatArray,
DoubleArray,
StateArray,
StringArray,
LongStringArray,
DoubleStringArray,
Encoded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrDataKind {
Bool,
Octet,
UShort,
Short,
ULong,
Long,
ULong64,
Long64,
Float,
Double,
State,
String,
Enum,
Encoded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrDataFormat {
Scalar,
Spectrum(usize),
Image(usize, usize),
}
pub trait AttrData {
const DATA_KIND: AttrDataKind;
const DATA_FORMAT: AttrDataFormat;
}
pub trait CommandData {
const DATA_KIND: CommandDataKind;
}
impl<T: TangoElement + ?Sized, const N: usize> AttrData for Buf<T, N> {
const DATA_KIND: AttrDataKind = T::ATTR_KIND;
const DATA_FORMAT: AttrDataFormat = match N {
0 => AttrDataFormat::Scalar,
1 => AttrDataFormat::Spectrum(0),
2 => AttrDataFormat::Image(0, 0),
_ => panic!("an attribute has at most two dimensions"),
};
}
impl<T: TangoElement + ?Sized, const N: usize> CommandData for Buf<T, N> {
const DATA_KIND: CommandDataKind = match (N, T::CMD_SCALAR_KIND, T::CMD_ARRAY_KIND) {
(0, Some(kind), _) | (1, _, Some(kind)) => kind,
(0 | 1, ..) => panic!("this element type cannot be a command argument"),
_ => panic!("a command argument has at most one dimension"),
};
}
macro_rules! impl_kinds_as {
($({$($generics:tt)*} $ty:ty => $data:ty;)*) => { $(
impl<$($generics)*> AttrData for $ty {
const DATA_KIND: AttrDataKind = <$data as AttrData>::DATA_KIND;
const DATA_FORMAT: AttrDataFormat = <$data as AttrData>::DATA_FORMAT;
}
impl<$($generics)*> CommandData for $ty {
const DATA_KIND: CommandDataKind = <$data as CommandData>::DATA_KIND;
}
)* };
}
impl_kinds_as! {
{T: TangoElement + PackedElement} T => Buf<T, 0>;
{T: TangoElement + PackedElement} Vec<T> => Buf<T, 1>;
{T: TangoElement + PackedElement} Box<[T]> => Buf<T, 1>;
{T: TangoElement + PackedElement, const N: usize} [T; N] => Buf<T, 1>;
{'a, T: TangoElement + PackedElement} &'a [T] => Buf<T, 1>;
{} String => Buf<str, 0>;
{'a} &'a str => Buf<str, 0>;
{} Vec<String> => Buf<str, 1>;
{'a, 'b} &'a [&'b str] => Buf<str, 1>;
}
impl CommandData for () {
const DATA_KIND: CommandDataKind = CommandDataKind::Void;
}
impl CommandData for (Buf<i32, 1>, Buf<str, 1>) {
const DATA_KIND: CommandDataKind = CommandDataKind::LongStringArray;
}
impl CommandData for (Buf<f64, 1>, Buf<str, 1>) {
const DATA_KIND: CommandDataKind = CommandDataKind::DoubleStringArray;
}
impl AttrData for DevEncoded {
const DATA_KIND: AttrDataKind = AttrDataKind::Encoded;
const DATA_FORMAT: AttrDataFormat = AttrDataFormat::Scalar;
}
impl CommandData for DevEncoded {
const DATA_KIND: CommandDataKind = CommandDataKind::Encoded;
}
impl DevEncoded {
pub const JPEG_RGB: &'static str = "JPEG_RGB";
pub const JPEG_GRAY8: &'static str = "JPEG_GRAY8";
pub const GRAY8: &'static str = "GRAY8";
pub const GRAY16: &'static str = "GRAY16";
pub const RGB24: &'static str = "RGB24";
}
#[derive(Debug, Clone, PartialEq)]
pub enum EncodedPixels {
Gray8(Image<u8>),
Gray16(Image<u16>),
Rgb24(Buf<u8, 3>),
}
impl DevEncoded {
pub fn to_pixels(&self) -> Result<EncodedPixels, Error> {
let format = self.format.value();
let channels = match format {
Self::GRAY8 => 1,
Self::GRAY16 => 2,
Self::RGB24 => 3,
format => return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Not an encoded image in a raw format: {format:?}"))),
};
let octets = self.data.as_slice();
let [w_hi, w_lo, h_hi, h_lo, ..] = *octets else {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("The {format} image has no dimensions")));
};
let width = u16::from_be_bytes([w_hi, w_lo]) as usize;
let height = u16::from_be_bytes([h_hi, h_lo]) as usize;
if octets.len() - 4 != width * height * channels {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("The {format} image of {width}x{height} pixels holds {} octets", octets.len() - 4)));
}
let pixels = self.data.slice(4..);
Ok(match channels {
1 => EncodedPixels::Gray8(pixels.reshape([width, height]).expect("as many pixels as the dimensions")),
2 => {
let values = pixels.as_slice().chunks_exact(2)
.map(|pair| u16::from_be_bytes([pair[0], pair[1]]))
.collect::<Vec<_>>();
EncodedPixels::Gray16(Image::from_slice(&values, [width, height]).expect("as many pixels as the dimensions"))
}
_ => EncodedPixels::Rgb24(pixels.reshape([3, width, height]).expect("as many pixels as the dimensions")),
})
}
pub fn from_pixels(pixels: &EncodedPixels) -> Result<Self, Error> {
let (format, [width, height]) = match pixels {
EncodedPixels::Gray8(image) => (Self::GRAY8, image.dim()),
EncodedPixels::Gray16(image) => (Self::GRAY16, image.dim()),
EncodedPixels::Rgb24(image) => {
let [_, width, height] = image.dim();
(Self::RGB24, [width, height])
}
};
let (Ok(width), Ok(height)) = (u16::try_from(width), u16::try_from(height)) else {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("A {width}x{height} image is too large to be encoded")));
};
let mut data = Vec::new();
data.extend_from_slice(&width.to_be_bytes());
data.extend_from_slice(&height.to_be_bytes());
match pixels {
EncodedPixels::Gray8(image) => data.extend_from_slice(image.as_slice()),
EncodedPixels::Gray16(image) => data.extend(image.as_slice().iter().flat_map(|pixel| pixel.to_be_bytes())),
EncodedPixels::Rgb24(image) => data.extend_from_slice(image.as_slice()),
}
Ok(Self::new(format, data))
}
#[cfg(feature = "image")]
pub fn to_image(&self) -> Result<image::DynamicImage, Error> {
use image::{DynamicImage, ImageBuffer, ImageFormat};
fn image_err(err: image::ImageError) -> Error {
Error::new(ErrorKind::InvalidData)
.with_description("The encoded image could not be decoded")
.with_source(Box::new(err))
}
let format = self.format.value();
if format == Self::JPEG_RGB || format == Self::JPEG_GRAY8 {
return image::load_from_memory_with_format(self.data.as_slice(), ImageFormat::Jpeg).map_err(image_err);
}
Ok(match self.to_pixels()? {
EncodedPixels::Gray8(pixels) => {
let [width, height] = pixels.dim();
DynamicImage::ImageLuma8(ImageBuffer::from_raw(width as u32, height as u32, pixels.as_slice().to_vec()).expect("as many pixels as the dimensions"))
}
EncodedPixels::Gray16(pixels) => {
let [width, height] = pixels.dim();
DynamicImage::ImageLuma16(ImageBuffer::from_raw(width as u32, height as u32, pixels.as_slice().to_vec()).expect("as many pixels as the dimensions"))
}
EncodedPixels::Rgb24(pixels) => {
let [_, width, height] = pixels.dim();
DynamicImage::ImageRgb8(ImageBuffer::from_raw(width as u32, height as u32, pixels.as_slice().to_vec()).expect("as many pixels as the dimensions"))
}
})
}
#[cfg(feature = "image")]
pub fn from_image(image: &image::DynamicImage) -> Result<Self, Error> {
use image::DynamicImage;
let dim = [image.width() as usize, image.height() as usize];
let pixels = match image {
DynamicImage::ImageLuma8(gray) => EncodedPixels::Gray8(Image::from_slice(gray.as_raw(), dim).expect("as many pixels as the dimensions")),
DynamicImage::ImageLuma16(gray) => EncodedPixels::Gray16(Image::from_slice(gray.as_raw(), dim).expect("as many pixels as the dimensions")),
image => EncodedPixels::Rgb24(Buf::from_slice(image.to_rgb8().as_raw(), [3, dim[0], dim[1]]).expect("as many pixels as the dimensions")),
};
Self::from_pixels(&pixels)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value() {
let spectrum = Buf::<f64, 1>::from(vec![1.0, 2.0]);
let data = Value::from(spectrum.clone());
assert!(!spectrum.is_unique());
assert_eq!(Buf::<f64, 1>::try_from(data).unwrap().as_slice(), &[1.0, 2.0]);
let values: Vec<f64> = Value::from(vec![1.0, 2.0]).try_into().unwrap();
assert_eq!(values, [1.0, 2.0]);
assert_eq!(i32::try_from(Value::from(3)).unwrap(), 3);
assert_eq!(String::try_from(Value::from("text")).unwrap(), "text");
assert_eq!(Vec::<String>::try_from(Value::from(&["a", "b"][..])).unwrap(), ["a", "b"]);
assert!(<()>::try_from(Value::from(())).is_ok());
assert_eq!(<[u8; 2]>::try_from(Value::from([1u8, 2])).unwrap(), [1, 2]);
let encoded = DevEncoded::new("RAW", vec![1u8, 2]);
assert_eq!(DevEncoded::try_from(Value::from(encoded.clone())).unwrap(), encoded);
assert!(DevEncoded::try_from(Value::from(vec![1u8, 2])).is_err());
assert!(<[u8; 3]>::try_from(Value::from([1u8, 2])).is_err());
}
#[test]
fn value_borrowed() {
let data = Value::from(vec![1u16, 2]);
assert_eq!(<&[u16]>::try_from(&data).unwrap(), &[1, 2]);
let data = Value::from("text");
assert_eq!(<&str>::try_from(&data).unwrap(), "text");
let data = Value::from(Buf::<u8, 2>::from_slice(&[1, 2, 3, 4], [2, 2]).unwrap());
assert_eq!(<&Buf<u8, 2>>::try_from(&data).unwrap().get([1, 1]), Some(4));
}
#[test]
fn value_mismatch() {
let err = Buf::<i32, 1>::try_from(Value::from(1.5)).unwrap_err();
assert_eq!(err.description(), "Expected a Long spectrum, got Double(Buf(1.5))");
assert!(f64::try_from(Value::from(vec![1.5])).is_err());
assert!(DevEnum::try_from(Value::from(1i16)).is_err());
}
#[test]
fn kinds() {
assert_eq!(<Buf<f64, 2> as AttrData>::DATA_FORMAT, AttrDataFormat::Image(0, 0));
assert_eq!(<Vec<f64> as AttrData>::DATA_FORMAT, AttrDataFormat::Spectrum(0));
assert_eq!(<Vec<f64> as CommandData>::DATA_KIND, CommandDataKind::DoubleArray);
assert_eq!(<&str as CommandData>::DATA_KIND, CommandDataKind::String);
assert_eq!(<DevEnum as AttrData>::DATA_KIND, AttrDataKind::Enum);
assert_eq!(<() as CommandData>::DATA_KIND, CommandDataKind::Void);
assert_eq!(<(Buf<i32, 1>, Buf<str, 1>) as CommandData>::DATA_KIND, CommandDataKind::LongStringArray);
assert_eq!(<DevEncoded as AttrData>::DATA_KIND, AttrDataKind::Encoded);
assert_eq!(<DevEncoded as AttrData>::DATA_FORMAT, AttrDataFormat::Scalar);
assert_eq!(<DevEncoded as CommandData>::DATA_KIND, CommandDataKind::Encoded);
}
#[test]
fn value_equality() {
let spectrum = Buf::<i32, 1>::from([1, 2, 3, 4]);
assert_eq!(Value::from(spectrum), Value::from(vec![1, 2, 3, 4]));
assert_ne!(Value::from(1i16), Value::from(DevEnum(1)));
}
#[test]
fn encoded_pixels() {
let gray8 = EncodedPixels::Gray8(Image::from_slice(&[1u8, 2, 3, 4, 5, 6], [3, 2]).unwrap());
let gray16 = EncodedPixels::Gray16(Image::from_slice(&[0x0102u16, 0xFFFE], [1, 2]).unwrap());
let rgb24 = EncodedPixels::Rgb24(Buf::from_slice(&[1u8, 2, 3, 4, 5, 6], [3, 2, 1]).unwrap());
for pixels in [gray8, gray16, rgb24] {
let encoded = DevEncoded::from_pixels(&pixels).unwrap();
assert_eq!(encoded.to_pixels().unwrap(), pixels);
}
let encoded = DevEncoded::from_pixels(&EncodedPixels::Gray16(Image::from_slice(&[0x0102u16], [1, 1]).unwrap())).unwrap();
assert_eq!(encoded.format.value(), DevEncoded::GRAY16);
assert_eq!(encoded.data.as_slice(), &[0, 1, 0, 1, 0x01, 0x02], "dimensions and pixels are big endian");
let encoded = DevEncoded::new(DevEncoded::GRAY8, vec![0u8, 2, 0, 1, 7, 8]);
let EncodedPixels::Gray8(image) = encoded.to_pixels().unwrap() else { panic!("not a GRAY8 image") };
assert_eq!(image.get([1, 0]), Some(8));
assert!(!encoded.data.is_unique(), "the pixels were copied out of the value");
}
#[test]
fn encoded_pixels_invalid() {
assert!(DevEncoded::new(DevEncoded::GRAY8, vec![0u8, 2, 0, 2, 1]).to_pixels().is_err());
assert!(DevEncoded::new(DevEncoded::RGB24, vec![0u8, 1]).to_pixels().is_err());
assert!(DevEncoded::new(DevEncoded::JPEG_RGB, vec![0xFFu8, 0xD8]).to_pixels().is_err());
let wide = EncodedPixels::Gray8(Image::from_slice(&vec![0u8; 70000], [70000, 1]).unwrap());
assert!(DevEncoded::from_pixels(&wide).is_err());
}
#[cfg(feature = "image")]
#[test]
fn encoded_image() {
use image::{DynamicImage, GrayImage, ImageBuffer, ImageFormat, Luma, RgbImage};
let gray8 = DynamicImage::ImageLuma8(GrayImage::from_raw(2, 1, vec![10, 20]).unwrap());
let gray16 = DynamicImage::ImageLuma16(ImageBuffer::<Luma<u16>, _>::from_raw(1, 2, vec![1000, 2000]).unwrap());
let rgb8 = DynamicImage::ImageRgb8(RgbImage::from_raw(1, 1, vec![1, 2, 3]).unwrap());
for image in [gray8, gray16, rgb8] {
assert_eq!(DevEncoded::from_image(&image).unwrap().to_image().unwrap(), image);
}
let mut jpeg = Vec::new();
DynamicImage::ImageRgb8(RgbImage::from_pixel(8, 4, [200, 100, 50].into()))
.write_to(&mut std::io::Cursor::new(&mut jpeg), ImageFormat::Jpeg)
.unwrap();
let image = DevEncoded::new(DevEncoded::JPEG_RGB, jpeg).to_image().unwrap();
assert_eq!((image.width(), image.height()), (8, 4));
}
}