From f1da843aaac9907e30832cfe15fcb1980f99163e Mon Sep 17 00:00:00 2001 From: Nick Babcock Date: Sun, 19 Nov 2023 13:07:24 -0600 Subject: [PATCH] Support binary rgb values with optional alpha component First seen in a late game vic3 save. Add `write_rgb` to text writer --- src/binary/de.rs | 44 ++++++++++++++++++++++--- src/binary/rgb.rs | 3 ++ src/binary/tape.rs | 82 ++++++++++++++++++++++++++++++++++++++-------- src/de.rs | 3 +- src/text/writer.rs | 40 +++++++++++++++++----- src/util.rs | 5 --- 6 files changed, 144 insertions(+), 33 deletions(-) diff --git a/src/binary/de.rs b/src/binary/de.rs index af4c1fb..865fede 100644 --- a/src/binary/de.rs +++ b/src/binary/de.rs @@ -183,11 +183,20 @@ impl<'data> OndemandParser<'data> { let g = self.read_u32()?; let btoken = self.read()?; let b = self.read_u32()?; - let end = self.read()?; - match (start, rtoken, gtoken, btoken, end) { - (OPEN, U32, U32, U32, END) => Ok(Rgb { r, g, b }), - _ => Err(self.invalid_syntax("invalid rgb value")), - } + let next_tok = self.read()?; + let a = match (start, rtoken, gtoken, btoken, next_tok) { + (OPEN, U32, U32, U32, END) => None, + (OPEN, U32, U32, U32, U32) => { + let a = Some(self.read_u32()?); + if self.read()? != END { + return Err(self.invalid_syntax("expected end after rgb alpha")); + } + a + } + _ => return Err(self.invalid_syntax("invalid rgb value")), + }; + + Ok(Rgb { r, g, b, a }) } #[cold] @@ -2718,6 +2727,31 @@ mod tests { } } + #[test] + fn test_deserialize_rgba() { + let data = [ + 0x3a, 0x05, 0x01, 0x00, 0x43, 0x02, 0x03, 0x00, 0x14, 0x00, 0x6e, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, + ]; + + let mut map = HashMap::new(); + map.insert(0x053a, "color"); + + let actual: MyStruct = from_slice(&data[..], &map).unwrap(); + assert_eq!( + actual, + MyStruct { + color: (String::from("rgb"), (110, 27, 27, 28)) + } + ); + + #[derive(Deserialize, Debug, PartialEq)] + struct MyStruct { + color: (String, (u8, u8, u8, u8)), + } + } + #[test] fn test_object_len() { let tokens = vec![ diff --git a/src/binary/rgb.rs b/src/binary/rgb.rs index b446c33..6441666 100644 --- a/src/binary/rgb.rs +++ b/src/binary/rgb.rs @@ -12,4 +12,7 @@ pub struct Rgb { /// Blue channel pub b: u32, + + /// Optional alpha channel + pub a: Option, } diff --git a/src/binary/tape.rs b/src/binary/tape.rs index 039b018..a34b819 100644 --- a/src/binary/tape.rs +++ b/src/binary/tape.rs @@ -1,10 +1,5 @@ use super::tokens::*; -use crate::{ - binary::Rgb, - copyless::VecHelper, - util::{get_split, le_u32}, - Error, ErrorKind, Scalar, -}; +use crate::{binary::Rgb, copyless::VecHelper, util::get_split, Error, ErrorKind, Scalar}; /// Represents any valid binary value #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -214,14 +209,42 @@ impl<'a, 'b> ParserState<'a, 'b> { } fn parse_rgb(&mut self, data: &'a [u8]) -> Result<&'a [u8], Error> { - let (head, rest) = get_split::<22>(data).ok_or_else(Error::eof)?; - let val = Rgb { - r: le_u32(&head[4..]), - g: le_u32(&head[10..]), - b: le_u32(&head[16..]), + let data = &data[2..]; + let (data, r_tok) = self.parse_next_id(data)?; + let (r_data, data) = get_split::<4>(data).ok_or_else(Error::eof)?; + let r = u32::from_le_bytes(r_data); + + let (data, g_tok) = self.parse_next_id(data)?; + let (g_data, data) = get_split::<4>(data).ok_or_else(Error::eof)?; + let g = u32::from_le_bytes(g_data); + + let (data, b_tok) = self.parse_next_id(data)?; + let (b_data, data) = get_split::<4>(data).ok_or_else(Error::eof)?; + let b = u32::from_le_bytes(b_data); + + if r_tok != U32 && g_tok != U32 && b_tok != U32 { + return Err(self.invalid_syntax("invalid rgb tokens", data)); + } + + let (data, next_tok) = self.parse_next_id(data)?; + + let (data, a) = match next_tok { + U32 => { + let (a_data, data) = get_split::<4>(data).ok_or_else(Error::eof)?; + let a = u32::from_le_bytes(a_data); + let (data, end_tok) = self.parse_next_id(data)?; + if end_tok != END { + return Err(self.invalid_syntax("expected end to follow rgb alpha", data)); + } + (data, Some(a)) + } + END => (data, None), + _ => return Err(self.invalid_syntax("invalid rgb end token", data)), }; + + let val = Rgb { r, g, b, a }; self.token_tape.alloc().init(BinaryToken::Rgb(val)); - Ok(rest) + Ok(data) } #[inline(always)] @@ -748,6 +771,15 @@ impl<'a, 'b> ParserState<'a, 'b> { offset: self.offset(data), }) } + + #[inline(never)] + #[cold] + fn invalid_syntax>(&self, msg: T, data: &[u8]) -> Error { + Error::new(ErrorKind::InvalidSyntax { + msg: msg.into(), + offset: self.offset(data), + }) + } } /// Houses the tape of tokens that is extracted from binary data @@ -784,9 +816,7 @@ mod tests { #[test] fn test_size_of_binary_token() { let token_size = std::mem::size_of::(); - let maxed = std::cmp::max(std::mem::size_of::(), std::mem::size_of::()); assert!(token_size <= 24); - assert_eq!(token_size, maxed + std::mem::size_of::()); } #[test] @@ -1199,6 +1229,7 @@ mod tests { r: 110, g: 27, b: 27, + a: None }) ] ); @@ -1214,6 +1245,29 @@ mod tests { ); } + #[test] + fn test_rgba() { + let data = [ + 0x3a, 0x05, 0x01, 0x00, 0x43, 0x02, 0x03, 0x00, 0x14, 0x00, 0x6e, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, + ]; + + let tape = parse(&data[..]).unwrap(); + assert_eq!( + tape.token_tape, + vec![ + BinaryToken::Token(0x053a), + BinaryToken::Rgb(Rgb { + r: 110, + g: 27, + b: 27, + a: Some(28) + }) + ] + ); + } + #[test] fn test_u64() { let data = [ diff --git a/src/de.rs b/src/de.rs index 0d63013..9c4117f 100644 --- a/src/de.rs +++ b/src/de.rs @@ -67,6 +67,7 @@ impl InnerColorSequence { 1 => self.data.r, 2 => self.data.g, 3 => self.data.b, + 4 => self.data.a.unwrap(), _ => unreachable!(), } } @@ -96,7 +97,7 @@ impl<'de> SeqAccess<'de> for InnerColorSequence { where T: DeserializeSeed<'de>, { - if self.idx >= 3 { + if (self.idx >= 3 && self.data.a.is_none()) || (self.idx >= 4 && self.data.a.is_some()) { Ok(None) } else { self.idx += 1; diff --git a/src/text/writer.rs b/src/text/writer.rs index b3d07fc..7e086d3 100644 --- a/src/text/writer.rs +++ b/src/text/writer.rs @@ -1,4 +1,5 @@ use crate::{ + binary::Rgb, common::PdsDateFormatter, text::{ArrayReader, ObjectReader, Operator, ValueReader}, BinaryToken, Encoding, Error, ErrorKind, TextTape, TextToken, @@ -434,6 +435,36 @@ where write!(self, "{}", data) } + /// Write an rgb value + /// + /// ``` + /// use jomini::{binary::Rgb, TextWriterBuilder}; + /// # fn main() -> Result<(), Box> { + /// let mut out: Vec = Vec::new(); + /// let mut writer = TextWriterBuilder::new().from_writer(&mut out); + /// writer.write_unquoted(b"start")?; + /// let val = Rgb { r: 10, g: 9, b: 8, a: None }; + /// writer.write_rgb(&val)?; + /// writer.write_unquoted(b"end")?; + /// + /// let val = Rgb { r: 7, g: 6, b: 5, a: Some(4) }; + /// writer.write_rgb(&val)?; + /// assert_eq!(&out, b"start=rgb {\n 10 9 8\n}\nend=rgb {\n 7 6 5 4\n}"); + /// # Ok(()) + /// # } + /// ``` + pub fn write_rgb(&mut self, color: &Rgb) -> Result<(), Error> { + self.write_header(b"rgb")?; + self.write_array_start()?; + self.write_u32(color.r)?; + self.write_u32(color.g)?; + self.write_u32(color.b)?; + if let Some(a) = color.a { + self.write_u32(a)?; + } + self.write_end() + } + /// Write formatted data /// /// Typically not invoked directly but instead through the `write!` macro @@ -578,14 +609,7 @@ where BinaryToken::Token(x) => { write!(self, "__unknown_0x{:x}", x) } - BinaryToken::Rgb(color) => { - self.write_header(b"rgb")?; - self.write_array_start()?; - self.write_u32(color.r)?; - self.write_u32(color.g)?; - self.write_u32(color.b)?; - self.write_end() - } + BinaryToken::Rgb(color) => self.write_rgb(color), } } diff --git a/src/util.rs b/src/util.rs index 02fc9dc..b112b30 100644 --- a/src/util.rs +++ b/src/util.rs @@ -27,11 +27,6 @@ pub(crate) const fn fast_digit_parse(val: u64) -> Option { } } -#[inline] -pub(crate) fn le_u32(data: &[u8]) -> u32 { - u32::from_le_bytes(take::<4>(data)) -} - #[inline] pub(crate) fn le_u64(data: &[u8]) -> u64 { u64::from_le_bytes(take::<8>(data))