lottieconv/lib.rs
1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(rust_2018_idioms)]
3#![deny(elided_lifetimes_in_paths, unreachable_pub)]
4
5//! Convert lottie animations to GIF or WEBP format.
6//!
7//! ## Examples
8//!
9//! This example shows how to use this crate to convert a lottie animation to a gif
10//! animation. This requires the `gif` feature which is enabled by default.
11//!
12//! ```rust,edition2018,no_run
13//! use lottieconv::{Animation, Converter, Rgba};
14//! use std::fs::File;
15//!
16//! # fn first() -> Option<()> {
17//! let animation = Animation::from_file("animation.json")?;
18//! # None
19//! # }
20//! # fn second() -> Result<(), std::io::Error> {
21//! let mut out = File::create("animation.gif")?;
22//! # Ok(())
23//! # }
24//! # fn third(animation: Animation, mut out: File) -> Result<(), gif_crate::EncodingError> {
25//! Converter::new(animation)
26//! .gif(Rgba::new_alpha(0, 0, 0, true), &mut out)?
27//! .convert()?;
28//! # Ok(())
29//! # }
30//! ```
31//!
32//! This example shows how to use this crate to convert a lottie animation to a webp
33//! animation. This requires the `webp` feature which is enabled by default.
34//!
35//! ```rust,edition2018,no_run
36//! use lottieconv::{Animation, Converter};
37//! use std::{fs::File, io::Write as _};
38//!
39//! # fn first() -> Option<()> {
40//! let animation = Animation::from_file("animation.json")?;
41//! # None
42//! # }
43//! # fn second(animation: Animation) -> Result<(), webp_animation::Error> {
44//! let webp_data = Converter::new(animation).webp()?.convert()?;
45//! # Ok(())
46//! # }
47//! # fn third(webp_data: webp_animation::WebPData) -> Result<(), std::io::Error> {
48//! let mut out = File::create("animation.webp")?;
49//! out.write_all(&webp_data)?;
50//! # Ok(())
51//! # }
52//! ```
53
54use rgb::RGBA8;
55use rlottie::Surface;
56use std::slice;
57
58#[doc(no_inline)]
59pub use rlottie::{Animation, Size};
60
61#[cfg(any(feature = "gif", feature = "webp"))]
62#[macro_use]
63mod util;
64
65mod convert;
66use convert::{Convert, DummyConvert};
67
68#[cfg(feature = "gif")]
69mod gif;
70#[cfg(feature = "gif")]
71use gif::Convert2Gif;
72
73#[cfg(feature = "webp")]
74mod webp;
75#[cfg(feature = "webp")]
76use webp::Convert2Webp;
77
78/// It is very important that [`RGBA8`] and `[u8; 4]` have exactly the same size.
79/// This mod does nothing other than fail to compile if that was not the case.
80#[allow(dead_code)]
81mod rgba_size {
82 use rgb::RGBA8;
83 use std::{marker::PhantomData, mem};
84
85 #[derive(Default)]
86 struct AssertSize<const N: usize>(PhantomData<[(); N]>);
87
88 impl<const N: usize> AssertSize<N> {
89 const fn new() -> Self {
90 Self(PhantomData)
91 }
92 }
93
94 impl AssertSize<4> {
95 const fn assert_size_u8_4(self) {}
96 }
97
98 const _: () = {
99 AssertSize::<{ mem::size_of::<RGBA8>() }>::new().assert_size_u8_4();
100 AssertSize::<{ mem::size_of::<[u8; 4]>() }>::new().assert_size_u8_4();
101 };
102}
103
104/// This type is used to perform the conversion. It does nothing unless you
105/// call [`.convert()`](Self::convert).
106#[must_use = "A Converter does nothing unless you call .convert()"]
107pub struct Converter<C: Convert> {
108 player: Animation,
109 size: Size,
110 convert: C
111}
112
113/// This type is used to build a [`Converter`]. It is created using
114/// [`Converter::new()`].
115pub struct Builder {
116 player: Animation,
117 size: Size
118}
119
120impl Converter<DummyConvert> {
121 /// Return a new converter builder.
122 #[allow(clippy::new_ret_no_self)]
123 #[must_use]
124 pub fn new(player: Animation) -> Builder {
125 Builder {
126 size: player.size(),
127 player
128 }
129 }
130}
131
132impl Builder {
133 /// Change the size of the output image.
134 #[must_use]
135 pub fn with_size(mut self, size: Size) -> Self {
136 self.size = size;
137 self
138 }
139}
140
141#[cfg(feature = "gif")]
142/// This type is used to define the background of a GIF.
143pub type Rgba = rgb::RGBA<u8, bool>;
144
145#[cfg(feature = "gif")]
146impl Builder {
147 /// Create a converter for lottie animation to a GIF file.
148 ///
149 /// **This is a lossy operation.**
150 /// GIF does not support full alpha channel. Even if you enable the alpha flag
151 /// for background color, the rgb value is required. This is because semi-transparent
152 /// pixels will be converted to non-transparent pixels, adding onto the background
153 /// color. Only fully transparent pixels will remain transparent.
154 pub fn gif<W: std::io::Write>(
155 self,
156 bg: Rgba,
157 out: W
158 ) -> gif::Result<Converter<Convert2Gif<W>>> {
159 let framerate = self.player.framerate();
160 Ok(Converter {
161 player: self.player,
162 size: self.size,
163 convert: Convert2Gif::new(bg, out, self.size, framerate)?
164 })
165 }
166}
167
168#[cfg(feature = "webp")]
169impl Builder {
170 /// Create a converter for lottie animation to a WEBP file.
171 pub fn webp(self) -> webp::Result<Converter<Convert2Webp>> {
172 let framerate = self.player.framerate();
173 Ok(Converter {
174 player: self.player,
175 size: self.size,
176 convert: Convert2Webp::new(self.size, framerate)?
177 })
178 }
179}
180
181impl<C: Convert> Converter<C> {
182 /// Convert lottie animation to the requested format.
183 ///
184 /// The return type of this function depends on the output format. For webp, it
185 /// returns the image data ([`WebPData`](webp_animation::WebPData)). For gif, it
186 /// returns `()` as the data is written directly to the writer that was passed
187 /// to the `.gif(..)` function.
188 pub fn convert(mut self) -> Result<C::Out, C::Err> {
189 let buffer_len = self.size.width * self.size.height;
190 let mut surface = Surface::new(self.size);
191 let mut buffer = vec![RGBA8::default(); buffer_len];
192 let frame_count = self.player.totalframe();
193
194 for frame in 0 .. frame_count {
195 self.player.render(frame, &mut surface);
196 self.convert.convert_frame(surface.data(), &mut buffer);
197
198 // Safety: The pointer is valid and aligned since it comes from a vec,
199 // and we don't use the vec while the slice exists.
200 let data = unsafe {
201 slice::from_raw_parts_mut(
202 buffer.as_mut_ptr() as *mut u8,
203 buffer_len * 4
204 )
205 };
206 self.convert.add_frame(data)?;
207 }
208
209 self.convert.finish()
210 }
211}