hvif/
style.rs

1// Copyright (c) 2021 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Describes the style of each shape.
8
9use crate::Transform;
10use bitflags::bitflags;
11use nom::{multi::many_m_n, number::complete::u8, sequence::tuple, IResult};
12
13/// Represents one colour, with optimisations in the cases there is no alpha,
14/// or all three components are equal (that is, grey).
15#[derive(Debug, Clone, PartialEq)]
16pub enum Colour {
17    /// Contains all four components.
18    Rgba(u8, u8, u8, u8),
19
20    /// Completely opaque RGB.
21    Rgb(u8, u8, u8),
22
23    /// All three colours are equal.
24    Grey(u8, u8),
25
26    /// Completely opaque grey.
27    GreyNoAlpha(u8),
28}
29
30impl Colour {
31    fn parse_rgba(i: &[u8]) -> IResult<&[u8], Colour> {
32        let (i, (r, g, b, a)) = tuple((u8, u8, u8, u8))(i)?;
33        Ok((i, Colour::Rgba(r, g, b, a)))
34    }
35
36    fn parse_rgb(i: &[u8]) -> IResult<&[u8], Colour> {
37        let (i, (r, g, b)) = tuple((u8, u8, u8))(i)?;
38        Ok((i, Colour::Rgb(r, g, b)))
39    }
40
41    fn parse_grey(i: &[u8]) -> IResult<&[u8], Colour> {
42        let (i, (grey, alpha)) = tuple((u8, u8))(i)?;
43        Ok((i, Colour::Grey(grey, alpha)))
44    }
45
46    fn parse_grey_no_alpha(i: &[u8]) -> IResult<&[u8], Colour> {
47        let (i, grey) = u8(i)?;
48        Ok((i, Colour::GreyNoAlpha(grey)))
49    }
50
51    /// Returns whether a colour is opaque, that is no alpha value is present.
52    pub fn is_opaque(&self) -> bool {
53        match *self {
54            Colour::Rgba(_, _, _, a) | Colour::Grey(_, a) => a == 255,
55            Colour::Rgb(_, _, _) | Colour::GreyNoAlpha(_) => true,
56        }
57    }
58
59    /// Converts this colour to its rgb components.
60    ///
61    /// Safety: it must be called only on opaque colours.
62    pub fn to_rgb(&self) -> (u8, u8, u8) {
63        match *self {
64            Colour::Rgba(r, g, b, a) if a == 255 => (r, g, b),
65            Colour::Rgb(r, g, b) => (r, g, b),
66            Colour::Grey(grey, alpha) if alpha == 255 => (grey, grey, grey),
67            Colour::GreyNoAlpha(grey) => (grey, grey, grey),
68            _ => panic!("This function must only be called on opaque colours."),
69        }
70    }
71
72    /// Converts this colour to its rgba components.
73    pub fn to_rgba(&self) -> (u8, u8, u8, u8) {
74        match *self {
75            Colour::Rgba(r, g, b, a) => (r, g, b, a),
76            Colour::Rgb(r, g, b) => (r, g, b, 255),
77            Colour::Grey(grey, alpha) => (grey, grey, grey, alpha),
78            Colour::GreyNoAlpha(grey) => (grey, grey, grey, 255),
79        }
80    }
81}
82
83enum StyleType {
84    SolidColour = 1,
85    Gradient = 2,
86    SolidColourNoAlpha = 3,
87    SolidGrey = 4,
88    SolidGreyNoAlpha = 5,
89}
90
91impl StyleType {
92    fn parse(i: &[u8]) -> IResult<&[u8], StyleType> {
93        let (i, type_) = u8(i)?;
94        use StyleType::*;
95        Ok((
96            i,
97            match type_ {
98                1 => SolidColour,
99                2 => Gradient,
100                3 => SolidColourNoAlpha,
101                4 => SolidGrey,
102                5 => SolidGreyNoAlpha,
103                _ => {
104                    unreachable!("Unknown style type {}", type_);
105                }
106            },
107        ))
108    }
109}
110
111/// The type of the gradient, for use for rendering.
112#[derive(Debug, Clone, Copy, PartialEq)]
113pub enum GradientType {
114    /// A linear gradient, interpolating linearly alongside the vector.
115    Linear = 0,
116
117    /// A circular gradient.
118    Circular = 1,
119
120    /// A gradient in a diamond shape.
121    Diamond = 2,
122
123    /// A conic gradient.
124    Conic = 3,
125
126    /// What is this even?
127    Xy = 4,
128
129    /// Aaaaah!
130    SqrtXy = 5,
131}
132
133impl GradientType {
134    fn parse(i: &[u8]) -> IResult<&[u8], GradientType> {
135        let (i, type_) = u8(i)?;
136        use GradientType::*;
137        Ok((
138            i,
139            match type_ {
140                0 => Linear,
141                1 => Circular,
142                2 => Diamond,
143                3 => Conic,
144                4 => Xy,
145                5 => SqrtXy,
146                _ => unreachable!("Unknown gradient type {}", type_),
147            },
148        ))
149    }
150}
151
152bitflags! {
153    /// Flags affecting a gradient.
154    pub struct GradientFlags: u8 {
155        /// This gradient contains a 3×2 transformation matrix.
156        const TRANSFORM = 0b00000010;
157
158        /// This gradient is fully opaque.
159        const NO_ALPHA = 0b00000100;
160
161        // Unused.
162        //const SIXTEEN_BIT_COLOURS = 0b00001000;
163
164        /// This gradient only uses greys, no other colours.
165        const GREYS = 0b00010000;
166    }
167}
168
169impl GradientFlags {
170    fn parse(i: &[u8]) -> IResult<&[u8], GradientFlags> {
171        let (i, flags) = u8(i)?;
172        Ok((i, GradientFlags::from_bits_truncate(flags)))
173    }
174}
175
176/// A point in a gradient.
177#[derive(Debug, Clone, PartialEq)]
178pub struct Stop {
179    /// The offset of this stop, interpolated.
180    pub offset: u8,
181
182    /// The colour of this stop.
183    pub colour: Colour,
184}
185
186impl Stop {
187    fn parse_rgba(i: &[u8]) -> IResult<&[u8], Stop> {
188        let (i, (offset, colour)) = tuple((u8, Colour::parse_rgba))(i)?;
189        Ok((i, Stop { offset, colour }))
190    }
191
192    fn parse_rgb(i: &[u8]) -> IResult<&[u8], Stop> {
193        let (i, (offset, colour)) = tuple((u8, Colour::parse_rgb))(i)?;
194        Ok((i, Stop { offset, colour }))
195    }
196
197    fn parse_grey(i: &[u8]) -> IResult<&[u8], Stop> {
198        let (i, (offset, colour)) = tuple((u8, Colour::parse_grey))(i)?;
199        Ok((i, Stop { offset, colour }))
200    }
201
202    fn parse_grey_no_alpha(i: &[u8]) -> IResult<&[u8], Stop> {
203        let (i, (offset, colour)) = tuple((u8, Colour::parse_grey_no_alpha))(i)?;
204        Ok((i, Stop { offset, colour }))
205    }
206}
207
208/// A gradient.
209#[derive(Debug, Clone, PartialEq)]
210pub struct Gradient {
211    /// The type of this gradient.
212    pub type_: GradientType,
213
214    /// The flags of this gradient.
215    pub flags: GradientFlags,
216
217    /// Contains up to 255 different stops in this gradient.
218    pub stops: Vec<Stop>,
219
220    /// An optional 3×2 transformation matrix applied to this gradient.
221    pub transform: Option<Transform>,
222}
223
224impl Gradient {
225    fn parse(i: &[u8]) -> IResult<&[u8], Gradient> {
226        let (i, (type_, flags)) = tuple((GradientType::parse, GradientFlags::parse))(i)?;
227        // length_count() can’t be used here, since we could have a transform matrix in-between.
228        let (i, num_stops) = u8(i)?;
229        let num_stops = num_stops as usize;
230        let (i, transform) = if flags.contains(GradientFlags::TRANSFORM) {
231            let (i, transform) = Transform::parse(i)?;
232            (i, Some(transform))
233        } else {
234            (i, None)
235        };
236        let (i, stops) = if flags.contains(GradientFlags::NO_ALPHA) {
237            if flags.contains(GradientFlags::GREYS) {
238                many_m_n(num_stops, num_stops, Stop::parse_grey_no_alpha)(i)?
239            } else {
240                many_m_n(num_stops, num_stops, Stop::parse_rgb)(i)?
241            }
242        } else if flags.contains(GradientFlags::GREYS) {
243            many_m_n(num_stops, num_stops, Stop::parse_grey)(i)?
244        } else {
245            many_m_n(num_stops, num_stops, Stop::parse_rgba)(i)?
246        };
247        Ok((
248            i,
249            Gradient {
250                type_,
251                flags,
252                stops,
253                transform,
254            },
255        ))
256    }
257}
258
259/// A style, to be applied to one or more shapes.
260#[derive(Debug, Clone, PartialEq)]
261pub enum Style {
262    /// This style is a solid colour.
263    SolidColour(Colour),
264
265    /// This style is a gradient.
266    Gradient(Gradient),
267}
268
269impl Style {
270    /// Parse a Style from its HVIF serialisation.
271    pub fn parse(i: &[u8]) -> IResult<&[u8], Style> {
272        let (i, type_) = StyleType::parse(i)?;
273        match type_ {
274            StyleType::SolidColour => {
275                let (i, colour) = Colour::parse_rgba(i)?;
276                Ok((i, Style::SolidColour(colour)))
277            }
278            StyleType::Gradient => {
279                let (i, gradient) = Gradient::parse(i)?;
280                Ok((i, Style::Gradient(gradient)))
281            }
282            StyleType::SolidColourNoAlpha => {
283                let (i, colour) = Colour::parse_rgb(i)?;
284                Ok((i, Style::SolidColour(colour)))
285            }
286            StyleType::SolidGrey => {
287                let (i, colour) = Colour::parse_grey(i)?;
288                Ok((i, Style::SolidColour(colour)))
289            }
290            StyleType::SolidGreyNoAlpha => {
291                let (i, colour) = Colour::parse_grey_no_alpha(i)?;
292                Ok((i, Style::SolidColour(colour)))
293            }
294        }
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301
302    macro_rules! assert_size (
303        ($t:ty, $sz:expr) => (
304            assert_eq!(::std::mem::size_of::<$t>(), $sz);
305        );
306    );
307
308    #[test]
309    fn sizes() {
310        assert_size!(Colour, 5);
311        assert_size!(GradientType, 1);
312        assert_size!(GradientFlags, 1);
313        assert_size!(Stop, 6);
314        assert_size!(Gradient, 56);
315        assert_size!(Style, 64);
316    }
317
318    #[test]
319    fn solid_grey() {
320        let grey = b"\x05\x80";
321        let (i, style) = Style::parse(grey).unwrap();
322        assert!(i.is_empty());
323        assert_eq!(style, Style::SolidColour(Colour::GreyNoAlpha(128)));
324    }
325
326    #[test]
327    fn solid_colour() {
328        let red = b"\x01\xff\x00\x00\x80";
329        let (i, style) = Style::parse(red).unwrap();
330        assert!(i.is_empty());
331        assert_eq!(style, Style::SolidColour(Colour::Rgba(255, 0, 0, 128)));
332    }
333
334    #[test]
335    fn gradient() {
336        let data = b"\x02\x00\x04\x02\x00\x20\x40\x60\xff\x60\x40\x20";
337        let (i, style) = Style::parse(data).unwrap();
338        println!("{:?}", i);
339        assert!(i.is_empty());
340        if let Style::Gradient(gradient) = style {
341            assert_eq!(gradient.type_, GradientType::Linear);
342            assert_eq!(gradient.flags, GradientFlags::NO_ALPHA);
343            assert_eq!(
344                gradient.stops,
345                [
346                    Stop {
347                        offset: 0,
348                        colour: Colour::Rgb(32, 64, 96)
349                    },
350                    Stop {
351                        offset: 255,
352                        colour: Colour::Rgb(96, 64, 32)
353                    },
354                ]
355            );
356            assert_eq!(gradient.transform, None);
357        } else {
358            panic!("Parsed style isn’t a gradient.");
359        }
360    }
361
362    #[test]
363    fn gradient_matrix() {
364        let data = b"\x02\x00\x02\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00";
365        let (i, style) = Style::parse(data).unwrap();
366        println!("{:?}", i);
367        assert!(i.is_empty());
368        if let Style::Gradient(gradient) = style {
369            assert_eq!(gradient.type_, GradientType::Linear);
370            assert_eq!(gradient.flags, GradientFlags::TRANSFORM);
371            assert_eq!(gradient.stops, []);
372            assert_eq!(gradient.transform.unwrap(), Transform::IDENTITY);
373        } else {
374            panic!("Parsed style isn’t a gradient.");
375        }
376    }
377}