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}