Skip to main content

tango/
value.rs

1//! The values Tango devices exchange: a [`Value`] is a [`Buf`] of any element type and
2//! dimensions Tango has, and what the arguments and results of handlers convert from and
3//! into.
4
5use std::fmt::{self, Debug};
6
7use crate::buf::{Buf, Element, PackedElement};
8use crate::error::{Error, ErrorKind};
9use crate::dev::DevState;
10
11
12// =========================== //
13//          Tango data         //
14// =========================== //
15
16/// A single value, see [`Buf`].
17pub type Scalar<T> = Buf<T, 0>;
18/// A one-dimensional array of values, see [`Buf`].
19pub type Spectrum<T> = Buf<T, 1>;
20/// A two-dimensional array of values, in column-major order, see [`Buf`].
21pub type Image<T> = Buf<T, 2>;
22
23/// A value of an enumerated attribute: the index of its label among the labels of the
24/// attribute, see [`crate::dev::AttrConfig::enum_labels`].
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
26#[repr(transparent)]
27pub struct DevEnum(pub i16);
28
29/// A value of an encoded attribute or command: octets, and the name of the format they
30/// are in, which is how a device sends what Tango has no type for, a compressed image
31/// above all. Both share the message they were decoded out of.
32///
33/// Tango only has such a value as a scalar, an attribute of it being neither a spectrum
34/// nor an image.
35#[derive(Debug, Clone, PartialEq)]
36pub struct DevEncoded {
37    /// The name of the format the octets are in, such as `JPEG_RGB`, which is only a
38    /// convention between the device and its clients.
39    pub format: Scalar<str>,
40    /// The octets themselves.
41    pub data: Spectrum<u8>,
42}
43
44impl DevEncoded {
45    /// An encoded value of that format, out of its octets.
46    #[inline]
47    pub fn new(format: impl Into<Scalar<str>>, data: impl Into<Spectrum<u8>>) -> Self {
48        Self { format: format.into(), data: data.into() }
49    }
50}
51
52/// Any value a Tango device exchanges, whatever its element type and dimensions: what a
53/// backend encodes and decodes, and what the arguments and results of handlers are
54/// converted from and into, with [`From`] and [`TryFrom`].
55#[derive(Debug, Clone, PartialEq)]
56pub enum Value {
57    Void,
58    Bool(ValueFormat<bool>),
59    Octet(ValueFormat<u8>),
60    UShort(ValueFormat<u16>),
61    Short(ValueFormat<i16>),
62    ULong(ValueFormat<u32>),
63    Long(ValueFormat<i32>),
64    ULong64(ValueFormat<u64>),
65    Long64(ValueFormat<i64>),
66    Float(ValueFormat<f32>),
67    Double(ValueFormat<f64>),
68    Enum(ValueFormat<DevEnum>),
69    State(ValueFormat<DevState>),
70    String(ValueFormat<str>),
71    /// The `DevVarLongStringArray` a command may take or give.
72    LongString(Spectrum<i32>, Spectrum<str>),
73    /// The `DevVarDoubleStringArray` a command may take or give.
74    DoubleString(Spectrum<f64>, Spectrum<str>),
75    /// A [`DevEncoded`], which only exists as a scalar.
76    Encoded(DevEncoded),
77}
78
79/// A data in any of the formats Tango has, as many dimensions as it has.
80pub enum ValueFormat<T: Element + ?Sized> {
81    Scalar(Scalar<T>),
82    Spectrum(Spectrum<T>),
83    Image(Image<T>),
84}
85
86macro_rules! impl_value_format {
87    ( $( $variant:ident: $get:ident, $into:ident; )* ) => {
88        impl<T: Element + ?Sized> ValueFormat<T> {
89            $(
90                #[doc = concat!("The data, if it is a ", stringify!($get), ".")]
91                #[inline]
92                pub fn $get(&self) -> Option<&$variant<T>> {
93                    match self {
94                        Self::$variant(data) => Some(data),
95                        _ => None,
96                    }
97                }
98
99                #[doc = concat!("The data, if it is a ", stringify!($get), ", or this format back.")]
100                #[inline]
101                pub fn $into(self) -> Result<$variant<T>, Self> {
102                    match self {
103                        Self::$variant(data) => Ok(data),
104                        format => Err(format),
105                    }
106                }
107            )*
108        }
109    };
110}
111
112impl_value_format! {
113    Scalar: scalar, into_scalar;
114    Spectrum: spectrum, into_spectrum;
115    Image: image, into_image;
116}
117
118impl<T: Element + ?Sized> Clone for ValueFormat<T> {
119    fn clone(&self) -> Self {
120        match self {
121            Self::Scalar(data) => Self::Scalar(data.clone()),
122            Self::Spectrum(data) => Self::Spectrum(data.clone()),
123            Self::Image(data) => Self::Image(data.clone()),
124        }
125    }
126}
127
128impl<T: Element + ?Sized> Debug for ValueFormat<T> {
129    /// The data it holds, whose dimensions already tell its format.
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        match self {
132            Self::Scalar(data) => data.fmt(f),
133            Self::Spectrum(data) => data.fmt(f),
134            Self::Image(data) => data.fmt(f),
135        }
136    }
137}
138
139impl<T: Element + ?Sized> PartialEq for ValueFormat<T>
140where
141    for<'a> T::Ref<'a>: PartialEq,
142{
143    fn eq(&self, other: &Self) -> bool {
144        match (self, other) {
145            (Self::Scalar(a), Self::Scalar(b)) => a == b,
146            (Self::Spectrum(a), Self::Spectrum(b)) => a == b,
147            (Self::Image(a), Self::Image(b)) => a == b,
148            _ => false,
149        }
150    }
151}
152
153/// An element type Tango has: which variant of [`Value`] holds it, and what kinds it
154/// is advertised as, see [`AttrData`] and [`CommandData`].
155pub trait TangoElement: Element {
156
157    /// The kind of an attribute of such values.
158    const ATTR_KIND: AttrDataKind;
159    /// The kind of a command argument of one such value, absent if none can be.
160    const CMD_SCALAR_KIND: Option<CommandDataKind>;
161    /// The kind of a command argument of an array of such values, absent if none can be.
162    const CMD_ARRAY_KIND: Option<CommandDataKind>;
163
164    fn wrap(format: ValueFormat<Self>) -> Value;
165    fn unwrap(data: Value) -> Result<ValueFormat<Self>, Value>;
166    fn unwrap_ref(data: &Value) -> Option<&ValueFormat<Self>>;
167
168}
169
170macro_rules! impl_tango_element {
171    ($($ty:ty => $variant:ident, $scalar:expr, $array:expr;)*) => { $(
172        impl TangoElement for $ty {
173
174            const ATTR_KIND: AttrDataKind = AttrDataKind::$variant;
175            const CMD_SCALAR_KIND: Option<CommandDataKind> = $scalar;
176            const CMD_ARRAY_KIND: Option<CommandDataKind> = $array;
177
178            #[inline]
179            fn wrap(format: ValueFormat<Self>) -> Value {
180                Value::$variant(format)
181            }
182
183            #[inline]
184            fn unwrap(data: Value) -> Result<ValueFormat<Self>, Value> {
185                match data {
186                    Value::$variant(format) => Ok(format),
187                    data => Err(data),
188                }
189            }
190
191            #[inline]
192            fn unwrap_ref(data: &Value) -> Option<&ValueFormat<Self>> {
193                match data {
194                    Value::$variant(format) => Some(format),
195                    _ => None,
196                }
197            }
198
199        }
200    )* };
201}
202
203impl_tango_element! {
204    bool => Bool, Some(CommandDataKind::Bool), Some(CommandDataKind::BoolArray);
205    u8 => Octet, Some(CommandDataKind::Octet), Some(CommandDataKind::OctetArray);
206    u16 => UShort, Some(CommandDataKind::UShort), Some(CommandDataKind::UShortArray);
207    i16 => Short, Some(CommandDataKind::Short), Some(CommandDataKind::ShortArray);
208    u32 => ULong, Some(CommandDataKind::ULong), Some(CommandDataKind::ULongArray);
209    i32 => Long, Some(CommandDataKind::Long), Some(CommandDataKind::LongArray);
210    u64 => ULong64, Some(CommandDataKind::ULong64), Some(CommandDataKind::ULong64Array);
211    i64 => Long64, Some(CommandDataKind::Long64), Some(CommandDataKind::Long64Array);
212    f32 => Float, Some(CommandDataKind::Float), Some(CommandDataKind::FloatArray);
213    f64 => Double, Some(CommandDataKind::Double), Some(CommandDataKind::DoubleArray);
214    DevEnum => Enum, None, None;
215    DevState => State, Some(CommandDataKind::State), Some(CommandDataKind::StateArray);
216    str => String, Some(CommandDataKind::String), Some(CommandDataKind::StringArray);
217}
218
219// =========================== //
220//         Conversions         //
221// =========================== //
222
223/// A type a value converts into, failing with an [`Error`] when it holds something else:
224/// every [`TryFrom<Value>`] whose error is one, [`Value`] itself included.
225///
226/// **Supporting trait**: it is only there to make this crate's API easier to use, it is
227/// implemented for everything that API accepts and is not one to implement anywhere else.
228pub trait FromValue: Sized {
229    fn from_value(value: Value) -> Result<Self, Error>;
230}
231
232impl<T> FromValue for T
233where
234    T: TryFrom<Value>,
235    T::Error: Into<Error>,
236{
237    #[inline]
238    fn from_value(value: Value) -> Result<Self, Error> {
239        T::try_from(value).map_err(Into::into)
240    }
241}
242
243/// A type that converts into a value: every [`Into<Value>`], [`Value`] itself included, see
244/// [`FromValue`] for the way back.
245///
246/// **Supporting trait**: it is only there to make this crate's API easier to use, it is
247/// implemented for everything that API accepts and is not one to implement anywhere else.
248pub trait IntoValue {
249    fn into_value(self) -> Value;
250}
251
252impl<T: Into<Value>> IntoValue for T {
253    #[inline]
254    fn into_value(self) -> Value {
255        self.into()
256    }
257}
258
259/// The conversions between a value and a data of each of the formats it may hold.
260macro_rules! impl_data_conversions {
261    ( $( $variant:ident: $get:ident, $into:ident; )* ) => {
262        $(
263            impl<T: TangoElement + ?Sized> From<$variant<T>> for Value {
264                #[inline]
265                fn from(data: $variant<T>) -> Self {
266                    T::wrap(ValueFormat::$variant(data))
267                }
268            }
269
270            impl<T: TangoElement + ?Sized> TryFrom<Value> for $variant<T> {
271                type Error = Error;
272                fn try_from(data: Value) -> Result<Self, Error> {
273                    T::unwrap(data)
274                        .and_then(|format| format.$into().map_err(T::wrap))
275                        .map_err(|data| Error::new(ErrorKind::InvalidData)
276                            .with_description(format!("Value is not a {:?} {}", T::ATTR_KIND, stringify!($get)))
277                            .with_append_description(format_args!("Got: {data:?}")))
278                }
279            }
280
281            impl<'a, T: TangoElement + ?Sized> TryFrom<&'a Value> for &'a $variant<T> {
282                type Error = Error;
283                fn try_from(data: &'a Value) -> Result<Self, Error> {
284                    T::unwrap_ref(data)
285                        .and_then(ValueFormat::$get)
286                        .ok_or_else(|| Error::new(ErrorKind::InvalidData)
287                            .with_description(format!("Value is not a {:?} {}", T::ATTR_KIND, stringify!($get)))
288                            .with_append_description(format_args!("Got: {data:?}")))
289                }
290            }
291        )*
292    };
293}
294
295impl_data_conversions! {
296    Scalar: scalar, into_scalar;
297    Spectrum: spectrum, into_spectrum;
298    Image: image, into_image;
299}
300
301impl<T: TangoElement + PackedElement> From<T> for Value {
302    #[inline]
303    fn from(value: T) -> Self {
304        Buf::<T, 0>::from(value).into()
305    }
306}
307
308impl<T: TangoElement + PackedElement> From<&[T]> for Value {
309    #[inline]
310    fn from(values: &[T]) -> Self {
311        Buf::from(values).into()
312    }
313}
314
315impl<T: TangoElement + PackedElement> From<Vec<T>> for Value {
316    #[inline]
317    fn from(values: Vec<T>) -> Self {
318        Buf::from(values).into()
319    }
320}
321
322impl<T: TangoElement + PackedElement> From<Box<[T]>> for Value {
323    #[inline]
324    fn from(values: Box<[T]>) -> Self {
325        Buf::from(values).into()
326    }
327}
328
329impl<T: TangoElement + PackedElement, const N: usize> From<[T; N]> for Value {
330    #[inline]
331    fn from(values: [T; N]) -> Self {
332        Buf::from(values).into()
333    }
334}
335
336impl From<&str> for Value {
337    #[inline]
338    fn from(value: &str) -> Self {
339        Buf::<str, 0>::from(value).into()
340    }
341}
342
343impl From<String> for Value {
344    #[inline]
345    fn from(value: String) -> Self {
346        Self::from(value.as_str())
347    }
348}
349
350impl<const N: usize> From<[&str; N]> for Value {
351    #[inline]
352    fn from(values: [&str; N]) -> Self {
353        Buf::<str, 1>::from(&values[..]).into()
354    }
355}
356
357impl From<&[&str]> for Value {
358    #[inline]
359    fn from(values: &[&str]) -> Self {
360        Buf::<str, 1>::from(values).into()
361    }
362}
363
364impl From<Vec<String>> for Value {
365    #[inline]
366    fn from(values: Vec<String>) -> Self {
367        Buf::<str, 1>::from(values.as_slice()).into()
368    }
369}
370
371impl From<()> for Value {
372    #[inline]
373    fn from((): ()) -> Self {
374        Self::Void
375    }
376}
377
378impl From<(Buf<i32, 1>, Buf<str, 1>)> for Value {
379    #[inline]
380    fn from((longs, strings): (Buf<i32, 1>, Buf<str, 1>)) -> Self {
381        Self::LongString(longs, strings)
382    }
383}
384
385impl From<(Buf<f64, 1>, Buf<str, 1>)> for Value {
386    #[inline]
387    fn from((doubles, strings): (Buf<f64, 1>, Buf<str, 1>)) -> Self {
388        Self::DoubleString(doubles, strings)
389    }
390}
391
392impl From<DevEncoded> for Value {
393    #[inline]
394    fn from(encoded: DevEncoded) -> Self {
395        Self::Encoded(encoded)
396    }
397}
398
399macro_rules! impl_scalar_try_from {
400    ($($ty:ty),*) => { $(
401        impl TryFrom<Value> for $ty {
402            type Error = Error;
403            #[inline]
404            fn try_from(data: Value) -> Result<Self, Error> {
405                Self::try_from(&data)
406            }
407        }
408
409        impl TryFrom<&Value> for $ty {
410            type Error = Error;
411            #[inline]
412            fn try_from(data: &Value) -> Result<Self, Error> {
413                <&Buf<$ty, 0>>::try_from(data).map(Buf::value)
414            }
415        }
416    )* };
417}
418
419impl_scalar_try_from!(bool, u8, u16, i16, u32, i32, u64, i64, f32, f64, DevEnum, DevState);
420
421impl<T: TangoElement + PackedElement> TryFrom<Value> for Vec<T> {
422    type Error = Error;
423    #[inline]
424    fn try_from(data: Value) -> Result<Self, Error> {
425        <&[T]>::try_from(&data).map(<[T]>::to_vec)
426    }
427}
428
429impl<T: TangoElement + PackedElement> TryFrom<Value> for Box<[T]> {
430    type Error = Error;
431    #[inline]
432    fn try_from(data: Value) -> Result<Self, Error> {
433        <&[T]>::try_from(&data).map(Box::from)
434    }
435}
436
437impl<T: TangoElement + PackedElement, const N: usize> TryFrom<Value> for [T; N] {
438    type Error = Error;
439    fn try_from(data: Value) -> Result<Self, Error> {
440        let values = <&[T]>::try_from(&data)?;
441        values.try_into().map_err(|_| Error::new(ErrorKind::InvalidData)
442            .with_description(format!("Spectrum holds {} values where {N} were expected", values.len())))
443    }
444}
445
446impl<'a, T: TangoElement + PackedElement> TryFrom<&'a Value> for &'a [T] {
447    type Error = Error;
448    #[inline]
449    fn try_from(data: &'a Value) -> Result<Self, Error> {
450        <&Buf<T, 1>>::try_from(data).map(Buf::as_slice)
451    }
452}
453
454impl TryFrom<Value> for String {
455    type Error = Error;
456    #[inline]
457    fn try_from(data: Value) -> Result<Self, Error> {
458        <&str>::try_from(&data).map(str::to_string)
459    }
460}
461
462impl<'a> TryFrom<&'a Value> for &'a str {
463    type Error = Error;
464    #[inline]
465    fn try_from(data: &'a Value) -> Result<Self, Error> {
466        <&Buf<str, 0>>::try_from(data).map(Buf::value)
467    }
468}
469
470impl TryFrom<Value> for Vec<String> {
471    type Error = Error;
472    #[inline]
473    fn try_from(data: Value) -> Result<Self, Error> {
474        <&Buf<str, 1>>::try_from(&data).map(|strings| strings.iter().map(str::to_string).collect())
475    }
476}
477
478impl TryFrom<Value> for () {
479    type Error = Error;
480    #[inline]
481    fn try_from(data: Value) -> Result<Self, Error> {
482        match data {
483            Value::Void => Ok(()),
484            data => Err(Error::new(ErrorKind::InvalidData)
485                .with_description("Value is not void")
486                .with_append_description(format_args!("Got: {data:?}"))),
487        }
488    }
489}
490
491impl TryFrom<Value> for (Buf<i32, 1>, Buf<str, 1>) {
492    type Error = Error;
493    #[inline]
494    fn try_from(data: Value) -> Result<Self, Error> {
495        match data {
496            Value::LongString(longs, strings) => Ok((longs, strings)),
497            data => Err(Error::new(ErrorKind::InvalidData)
498                .with_description("Value is not a long string array")
499                .with_append_description(format_args!("Got: {data:?}"))),
500        }
501    }
502}
503
504impl TryFrom<Value> for (Buf<f64, 1>, Buf<str, 1>) {
505    type Error = Error;
506    #[inline]
507    fn try_from(data: Value) -> Result<Self, Error> {
508        match data {
509            Value::DoubleString(doubles, strings) => Ok((doubles, strings)),
510            data => Err(Error::new(ErrorKind::InvalidData)
511                .with_description("Value is not a double string array")
512                .with_append_description(format_args!("Got: {data:?}"))),
513        }
514    }
515}
516
517impl TryFrom<Value> for DevEncoded {
518    type Error = Error;
519    #[inline]
520    fn try_from(data: Value) -> Result<Self, Error> {
521        match data {
522            Value::Encoded(encoded) => Ok(encoded),
523            data => Err(Error::new(ErrorKind::InvalidData)
524                .with_description("Value is not encoded")
525                .with_append_description(format_args!("Got: {data:?}"))),
526        }
527    }
528}
529
530impl<'a> TryFrom<&'a Value> for &'a DevEncoded {
531    type Error = Error;
532    #[inline]
533    fn try_from(data: &'a Value) -> Result<Self, Error> {
534        match data {
535            Value::Encoded(encoded) => Ok(encoded),
536            data => Err(Error::new(ErrorKind::InvalidData)
537                .with_description("Value is not encoded")
538                .with_append_description(format_args!("Got: {data:?}"))),
539        }
540    }
541}
542
543// =========================== //
544//            Kinds            //
545// =========================== //
546
547/// The data kind of a Tango command argument. This is the closed set of types accepted
548/// by `command_inout`, without any layout information (see [`CommandData`]).
549#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
550pub enum CommandDataKind {
551    Void,
552    Bool,
553    Octet,
554    UShort,
555    Short,
556    ULong,
557    Long,
558    ULong64,
559    Long64,
560    Float,
561    Double,
562    State,
563    String,
564    BoolArray,
565    OctetArray,
566    UShortArray,
567    ShortArray,
568    ULongArray,
569    LongArray,
570    ULong64Array,
571    Long64Array,
572    FloatArray,
573    DoubleArray,
574    StateArray,
575    StringArray,
576    LongStringArray,
577    DoubleStringArray,
578    Encoded,
579}
580
581/// The element type of a Tango attribute value, its layout being an [`AttrDataFormat`].
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
583pub enum AttrDataKind {
584    Bool,
585    Octet,
586    UShort,
587    Short,
588    ULong,
589    Long,
590    ULong64,
591    Long64,
592    Float,
593    Double,
594    State,
595    String,
596    /// An enumerated value, which travels as the index of its own label in the list of
597    /// them: what those labels are is a property of the attribute a client may change, and
598    /// lives in its configuration, see [`crate::dev::AttrConfig::enum_labels`].
599    Enum,
600    /// A [`DevEncoded`], only ever a scalar.
601    Encoded,
602}
603
604/// The layout of a Tango attribute value.
605/// 
606/// The maximum size for spectrum and image formats is also given, those can be set to 0
607/// when there is no maximum.
608#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
609pub enum AttrDataFormat {
610    Scalar,
611    Spectrum(usize),
612    Image(usize, usize),
613}
614
615/// A type an attribute value can be, and what kind and format it is advertised as.
616pub trait AttrData {
617    const DATA_KIND: AttrDataKind;
618    const DATA_FORMAT: AttrDataFormat;
619}
620
621/// A type a command argument or result can be, and what kind it is advertised as.
622pub trait CommandData {
623    const DATA_KIND: CommandDataKind;
624}
625
626impl<T: TangoElement + ?Sized, const N: usize> AttrData for Buf<T, N> {
627    const DATA_KIND: AttrDataKind = T::ATTR_KIND;
628    const DATA_FORMAT: AttrDataFormat = match N {
629        0 => AttrDataFormat::Scalar,
630        1 => AttrDataFormat::Spectrum(0),
631        2 => AttrDataFormat::Image(0, 0),
632        _ => panic!("an attribute has at most two dimensions"),
633    };
634}
635
636impl<T: TangoElement + ?Sized, const N: usize> CommandData for Buf<T, N> {
637    const DATA_KIND: CommandDataKind = match (N, T::CMD_SCALAR_KIND, T::CMD_ARRAY_KIND) {
638        (0, Some(kind), _) | (1, _, Some(kind)) => kind,
639        (0 | 1, ..) => panic!("this element type cannot be a command argument"),
640        _ => panic!("a command argument has at most one dimension"),
641    };
642}
643
644/// Implement the kinds of these types as those of the data they convert to.
645macro_rules! impl_kinds_as {
646    ($({$($generics:tt)*} $ty:ty => $data:ty;)*) => { $(
647        impl<$($generics)*> AttrData for $ty {
648            const DATA_KIND: AttrDataKind = <$data as AttrData>::DATA_KIND;
649            const DATA_FORMAT: AttrDataFormat = <$data as AttrData>::DATA_FORMAT;
650        }
651
652        impl<$($generics)*> CommandData for $ty {
653            const DATA_KIND: CommandDataKind = <$data as CommandData>::DATA_KIND;
654        }
655    )* };
656}
657
658impl_kinds_as! {
659    {T: TangoElement + PackedElement} T => Buf<T, 0>;
660    {T: TangoElement + PackedElement} Vec<T> => Buf<T, 1>;
661    {T: TangoElement + PackedElement} Box<[T]> => Buf<T, 1>;
662    {T: TangoElement + PackedElement, const N: usize} [T; N] => Buf<T, 1>;
663    {'a, T: TangoElement + PackedElement} &'a [T] => Buf<T, 1>;
664    {} String => Buf<str, 0>;
665    {'a} &'a str => Buf<str, 0>;
666    {} Vec<String> => Buf<str, 1>;
667    {'a, 'b} &'a [&'b str] => Buf<str, 1>;
668}
669
670impl CommandData for () {
671    const DATA_KIND: CommandDataKind = CommandDataKind::Void;
672}
673
674impl CommandData for (Buf<i32, 1>, Buf<str, 1>) {
675    const DATA_KIND: CommandDataKind = CommandDataKind::LongStringArray;
676}
677
678impl CommandData for (Buf<f64, 1>, Buf<str, 1>) {
679    const DATA_KIND: CommandDataKind = CommandDataKind::DoubleStringArray;
680}
681
682impl AttrData for DevEncoded {
683    const DATA_KIND: AttrDataKind = AttrDataKind::Encoded;
684    const DATA_FORMAT: AttrDataFormat = AttrDataFormat::Scalar;
685}
686
687impl CommandData for DevEncoded {
688    const DATA_KIND: CommandDataKind = CommandDataKind::Encoded;
689}
690
691
692// =========================== //
693//       Encoded images        //
694// =========================== //
695
696impl DevEncoded {
697    /// The format of a JPEG image in color, see [`Self::to_image`].
698    pub const JPEG_RGB: &'static str = "JPEG_RGB";
699    /// The format of a JPEG image in shades of gray, see [`Self::to_image`].
700    pub const JPEG_GRAY8: &'static str = "JPEG_GRAY8";
701    /// The format of raw 8 bits gray pixels, see [`EncodedPixels::Gray8`].
702    pub const GRAY8: &'static str = "GRAY8";
703    /// The format of raw 16 bits gray pixels, see [`EncodedPixels::Gray16`].
704    pub const GRAY16: &'static str = "GRAY16";
705    /// The format of raw 24 bits color pixels, see [`EncodedPixels::Rgb24`].
706    pub const RGB24: &'static str = "RGB24";
707}
708
709/// The pixels of an image in one of the raw formats cppTango encodes images in, rows after
710/// rows: in the dimensions of an image, the width comes first and varies fastest, the way
711/// it does in an image attribute.
712///
713/// On the wire, such an image is its width and its height, both as big endian 16 bits
714/// integers, and then its pixels.
715#[derive(Debug, Clone, PartialEq)]
716pub enum EncodedPixels {
717    /// A [`DevEncoded::GRAY8`] image, sharing the octets of the value.
718    Gray8(Image<u8>),
719    /// A [`DevEncoded::GRAY16`] image, whose big endian pixels are converted.
720    Gray16(Image<u16>),
721    /// A [`DevEncoded::RGB24`] image, sharing the octets of the value, the three components
722    /// of a pixel being the first dimension, then the width, then the height.
723    Rgb24(Buf<u8, 3>),
724}
725
726impl DevEncoded {
727
728    /// The pixels of an image in one of the raw formats, failing for any other format,
729    /// JPEG included, see [`Self::to_image`] for that one.
730    pub fn to_pixels(&self) -> Result<EncodedPixels, Error> {
731
732        let format = self.format.value();
733        let channels = match format {
734            Self::GRAY8 => 1,
735            Self::GRAY16 => 2,
736            Self::RGB24 => 3,
737            format => return Err(Error::new(ErrorKind::InvalidData)
738                .with_description(format!("Encoded format {format:?} is not a raw image format"))),
739        };
740
741        let octets = self.data.as_slice();
742        let [w_hi, w_lo, h_hi, h_lo, ..] = *octets else {
743            return Err(Error::new(ErrorKind::InvalidData)
744                .with_description(format!("Encoded {format} image has no dimensions")));
745        };
746
747        let width = u16::from_be_bytes([w_hi, w_lo]) as usize;
748        let height = u16::from_be_bytes([h_hi, h_lo]) as usize;
749        if octets.len() - 4 != width * height * channels {
750            return Err(Error::new(ErrorKind::InvalidData)
751                .with_description(format!("Encoded {format} image of {width}x{height} pixels holds {} octets where {} were expected",
752                    octets.len() - 4, width * height * channels)));
753        }
754
755        let pixels = self.data.slice(4..);
756        Ok(match channels {
757            1 => EncodedPixels::Gray8(pixels.reshape([width, height]).expect("as many pixels as the dimensions")),
758            2 => {
759                let values = pixels.as_slice().as_chunks::<2>().0.iter()
760                    .map(|pair| u16::from_be_bytes([pair[0], pair[1]]))
761                    .collect::<Vec<_>>();
762                EncodedPixels::Gray16(Image::from_slice(&values, [width, height]).expect("as many pixels as the dimensions"))
763            }
764            _ => EncodedPixels::Rgb24(pixels.reshape([3, width, height]).expect("as many pixels as the dimensions")),
765        })
766
767    }
768
769    /// Encode these pixels in their raw format, failing for an image wider or higher than
770    /// the 65535 pixels its dimensions have room for.
771    pub fn from_pixels(pixels: &EncodedPixels) -> Result<Self, Error> {
772
773        let (format, [width, height]) = match pixels {
774            EncodedPixels::Gray8(image) => (Self::GRAY8, image.dim()),
775            EncodedPixels::Gray16(image) => (Self::GRAY16, image.dim()),
776            EncodedPixels::Rgb24(image) => {
777                let [_, width, height] = image.dim();
778                (Self::RGB24, [width, height])
779            }
780        };
781
782        let (Ok(width), Ok(height)) = (u16::try_from(width), u16::try_from(height)) else {
783            return Err(Error::new(ErrorKind::InvalidData)
784                .with_description(format!("Image of {width}x{height} pixels is too large to be encoded")));
785        };
786
787        let mut data = Vec::new();
788        data.extend_from_slice(&width.to_be_bytes());
789        data.extend_from_slice(&height.to_be_bytes());
790        match pixels {
791            EncodedPixels::Gray8(image) => data.extend_from_slice(image.as_slice()),
792            EncodedPixels::Gray16(image) => data.extend(image.as_slice().iter().flat_map(|pixel| pixel.to_be_bytes())),
793            EncodedPixels::Rgb24(image) => data.extend_from_slice(image.as_slice()),
794        }
795
796        Ok(Self::new(format, data))
797
798    }
799
800    /// The image, in any of the formats cppTango encodes images in, JPEG included.
801    #[cfg(feature = "image")]
802    pub fn to_image(&self) -> Result<image::DynamicImage, Error> {
803
804        use image::{DynamicImage, ImageBuffer, ImageFormat};
805
806        fn image_err(err: image::ImageError) -> Error {
807            Error::new(ErrorKind::InvalidData)
808                .with_description("Failed to decode encoded image")
809                .with_source(Box::new(err))
810        }
811
812        let format = self.format.value();
813        if format == Self::JPEG_RGB || format == Self::JPEG_GRAY8 {
814            return image::load_from_memory_with_format(self.data.as_slice(), ImageFormat::Jpeg).map_err(image_err);
815        }
816
817        Ok(match self.to_pixels()? {
818            EncodedPixels::Gray8(pixels) => {
819                let [width, height] = pixels.dim();
820                DynamicImage::ImageLuma8(ImageBuffer::from_raw(width as u32, height as u32, pixels.as_slice().to_vec()).expect("as many pixels as the dimensions"))
821            }
822            EncodedPixels::Gray16(pixels) => {
823                let [width, height] = pixels.dim();
824                DynamicImage::ImageLuma16(ImageBuffer::from_raw(width as u32, height as u32, pixels.as_slice().to_vec()).expect("as many pixels as the dimensions"))
825            }
826            EncodedPixels::Rgb24(pixels) => {
827                let [_, width, height] = pixels.dim();
828                DynamicImage::ImageRgb8(ImageBuffer::from_raw(width as u32, height as u32, pixels.as_slice().to_vec()).expect("as many pixels as the dimensions"))
829            }
830        })
831
832    }
833
834    /// Encode the image in the raw format closest to it: gray images keep their depth, and
835    /// any other one is converted to 8 bits color.
836    #[cfg(feature = "image")]
837    pub fn from_image(image: &image::DynamicImage) -> Result<Self, Error> {
838
839        use image::DynamicImage;
840
841        let dim = [image.width() as usize, image.height() as usize];
842        let pixels = match image {
843            DynamicImage::ImageLuma8(gray) => EncodedPixels::Gray8(Image::from_slice(gray.as_raw(), dim).expect("as many pixels as the dimensions")),
844            DynamicImage::ImageLuma16(gray) => EncodedPixels::Gray16(Image::from_slice(gray.as_raw(), dim).expect("as many pixels as the dimensions")),
845            image => EncodedPixels::Rgb24(Buf::from_slice(image.to_rgb8().as_raw(), [3, dim[0], dim[1]]).expect("as many pixels as the dimensions")),
846        };
847
848        Self::from_pixels(&pixels)
849
850    }
851
852}
853
854
855#[cfg(test)]
856mod tests {
857
858    use super::*;
859
860    /// A typed data converts into a value and back without copying, while the usual
861    /// owned types convert by copying their values.
862    #[test]
863    fn value() {
864        let spectrum = Buf::<f64, 1>::from(vec![1.0, 2.0]);
865        let data = Value::from(spectrum.clone());
866        assert!(!spectrum.is_unique());
867        assert_eq!(Buf::<f64, 1>::try_from(data).unwrap().as_slice(), &[1.0, 2.0]);
868
869        let values: Vec<f64> = Value::from(vec![1.0, 2.0]).try_into().unwrap();
870        assert_eq!(values, [1.0, 2.0]);
871        assert_eq!(i32::try_from(Value::from(3)).unwrap(), 3);
872        assert_eq!(String::try_from(Value::from("text")).unwrap(), "text");
873        assert_eq!(Vec::<String>::try_from(Value::from(&["a", "b"][..])).unwrap(), ["a", "b"]);
874        assert!(<()>::try_from(Value::from(())).is_ok());
875        assert_eq!(<[u8; 2]>::try_from(Value::from([1u8, 2])).unwrap(), [1, 2]);
876        let encoded = DevEncoded::new("RAW", vec![1u8, 2]);
877        assert_eq!(DevEncoded::try_from(Value::from(encoded.clone())).unwrap(), encoded);
878        assert!(DevEncoded::try_from(Value::from(vec![1u8, 2])).is_err());
879        assert!(<[u8; 3]>::try_from(Value::from([1u8, 2])).is_err());
880    }
881
882    /// A reference to a value converts into a reference to what it holds.
883    #[test]
884    fn value_borrowed() {
885        let data = Value::from(vec![1u16, 2]);
886        assert_eq!(<&[u16]>::try_from(&data).unwrap(), &[1, 2]);
887        let data = Value::from("text");
888        assert_eq!(<&str>::try_from(&data).unwrap(), "text");
889        let data = Value::from(Buf::<u8, 2>::from_slice(&[1, 2, 3, 4], [2, 2]).unwrap());
890        assert_eq!(<&Buf<u8, 2>>::try_from(&data).unwrap().get([1, 1]), Some(4));
891    }
892
893    /// Converting into another element type or other dimensions fails, saying what was
894    /// expected and what was there.
895    #[test]
896    fn value_mismatch() {
897        let err = Buf::<i32, 1>::try_from(Value::from(1.5)).unwrap_err();
898        assert_eq!(err.description(), "Value is not a Long spectrum\nGot: Double(Buf(1.5))");
899        assert!(f64::try_from(Value::from(vec![1.5])).is_err());
900        // A short is not an enumerated value, even though both are 16 bits.
901        assert!(DevEnum::try_from(Value::from(1i16)).is_err());
902    }
903
904    /// The kinds a type is advertised as follow from its element type and dimensions.
905    #[test]
906    fn kinds() {
907        assert_eq!(<Buf<f64, 2> as AttrData>::DATA_FORMAT, AttrDataFormat::Image(0, 0));
908        assert_eq!(<Vec<f64> as AttrData>::DATA_FORMAT, AttrDataFormat::Spectrum(0));
909        assert_eq!(<Vec<f64> as CommandData>::DATA_KIND, CommandDataKind::DoubleArray);
910        assert_eq!(<&str as CommandData>::DATA_KIND, CommandDataKind::String);
911        assert_eq!(<DevEnum as AttrData>::DATA_KIND, AttrDataKind::Enum);
912        assert_eq!(<() as CommandData>::DATA_KIND, CommandDataKind::Void);
913        assert_eq!(<(Buf<i32, 1>, Buf<str, 1>) as CommandData>::DATA_KIND, CommandDataKind::LongStringArray);
914        assert_eq!(<DevEncoded as AttrData>::DATA_KIND, AttrDataKind::Encoded);
915        assert_eq!(<DevEncoded as AttrData>::DATA_FORMAT, AttrDataFormat::Scalar);
916        assert_eq!(<DevEncoded as CommandData>::DATA_KIND, CommandDataKind::Encoded);
917    }
918
919    /// Values are equal when they hold the same data, wherever it is stored, and of the
920    /// same element type.
921    #[test]
922    fn value_equality() {
923        let spectrum = Buf::<i32, 1>::from([1, 2, 3, 4]);
924        assert_eq!(Value::from(spectrum), Value::from(vec![1, 2, 3, 4]));
925        assert_ne!(Value::from(1i16), Value::from(DevEnum(1)));
926    }
927
928    /// The raw formats go out with their dimensions ahead of their pixels and come back as
929    /// they were, the 8 bits ones sharing the octets of the value.
930    #[test]
931    fn encoded_pixels() {
932
933        let gray8 = EncodedPixels::Gray8(Image::from_slice(&[1u8, 2, 3, 4, 5, 6], [3, 2]).unwrap());
934        let gray16 = EncodedPixels::Gray16(Image::from_slice(&[0x0102u16, 0xFFFE], [1, 2]).unwrap());
935        let rgb24 = EncodedPixels::Rgb24(Buf::from_slice(&[1u8, 2, 3, 4, 5, 6], [3, 2, 1]).unwrap());
936
937        for pixels in [gray8, gray16, rgb24] {
938            let encoded = DevEncoded::from_pixels(&pixels).unwrap();
939            assert_eq!(encoded.to_pixels().unwrap(), pixels);
940        }
941
942        let encoded = DevEncoded::from_pixels(&EncodedPixels::Gray16(Image::from_slice(&[0x0102u16], [1, 1]).unwrap())).unwrap();
943        assert_eq!(encoded.format.value(), DevEncoded::GRAY16);
944        assert_eq!(encoded.data.as_slice(), &[0, 1, 0, 1, 0x01, 0x02], "dimensions and pixels are big endian");
945
946        let encoded = DevEncoded::new(DevEncoded::GRAY8, vec![0u8, 2, 0, 1, 7, 8]);
947        let EncodedPixels::Gray8(image) = encoded.to_pixels().unwrap() else { panic!("not a GRAY8 image") };
948        assert_eq!(image.get([1, 0]), Some(8));
949        assert!(!encoded.data.is_unique(), "the pixels were copied out of the value");
950
951    }
952
953    /// An image whose octets do not add up to its dimensions, or in a format that is not a
954    /// raw one, is refused.
955    #[test]
956    fn encoded_pixels_invalid() {
957        assert!(DevEncoded::new(DevEncoded::GRAY8, vec![0u8, 2, 0, 2, 1]).to_pixels().is_err());
958        assert!(DevEncoded::new(DevEncoded::RGB24, vec![0u8, 1]).to_pixels().is_err());
959        assert!(DevEncoded::new(DevEncoded::JPEG_RGB, vec![0xFFu8, 0xD8]).to_pixels().is_err());
960        let wide = EncodedPixels::Gray8(Image::from_slice(&vec![0u8; 70000], [70000, 1]).unwrap());
961        assert!(DevEncoded::from_pixels(&wide).is_err());
962    }
963
964    /// An image goes through its closest raw format and comes back as it was, and a JPEG
965    /// one is decoded whatever its depth.
966    #[cfg(feature = "image")]
967    #[test]
968    fn encoded_image() {
969
970        use image::{DynamicImage, GrayImage, ImageBuffer, ImageFormat, Luma, RgbImage};
971
972        let gray8 = DynamicImage::ImageLuma8(GrayImage::from_raw(2, 1, vec![10, 20]).unwrap());
973        let gray16 = DynamicImage::ImageLuma16(ImageBuffer::<Luma<u16>, _>::from_raw(1, 2, vec![1000, 2000]).unwrap());
974        let rgb8 = DynamicImage::ImageRgb8(RgbImage::from_raw(1, 1, vec![1, 2, 3]).unwrap());
975
976        for image in [gray8, gray16, rgb8] {
977            assert_eq!(DevEncoded::from_image(&image).unwrap().to_image().unwrap(), image);
978        }
979
980        let mut jpeg = Vec::new();
981        DynamicImage::ImageRgb8(RgbImage::from_pixel(8, 4, [200, 100, 50].into()))
982            .write_to(&mut std::io::Cursor::new(&mut jpeg), ImageFormat::Jpeg)
983            .unwrap();
984        let image = DevEncoded::new(DevEncoded::JPEG_RGB, jpeg).to_image().unwrap();
985        assert_eq!((image.width(), image.height()), (8, 4));
986
987    }
988
989}