rlottie/
lib.rs

1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(rust_2018_idioms)]
3#![deny(elided_lifetimes_in_paths, unreachable_pub)]
4
5//! Safe Rust bindings to rlottie.
6//!
7//! # Example
8//!
9//! ```rust,edition2021,no_run
10//! use rlottie::{Animation, Surface};
11//!
12//! # fn first(path_to_lottie_json: std::path::PathBuf) -> Option<()> {
13//! let mut animation = Animation::from_file(path_to_lottie_json)?;
14//! # Some(())
15//! # }
16//! # fn second(mut animation: Animation) {
17//! let size = animation.size();
18//! let mut surface = Surface::new(size);
19//! for frame in 0 .. animation.totalframe() {
20//! 	animation.render(frame, &mut surface);
21//! 	for (x, y, color) in surface.pixels() {
22//! 		println!("frame {frame} at ({x}, {y}): {color:?}");
23//! 	}
24//! }
25//! # }
26//! ```
27
28use rgb::{RGB, alt::BGRA};
29use rlottie_sys::*;
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32use std::{
33	ffi::CString,
34	fmt::{self, Debug},
35	mem,
36	os::unix::ffi::OsStrExt,
37	path::Path,
38	ptr::NonNull,
39	slice
40};
41
42pub type Bgra<T = u8> = BGRA<T>;
43pub type Rgb<T = f64> = RGB<T>;
44
45fn color_is_valid(Rgb { r, g, b }: Rgb) -> bool {
46	(0.0 ..= 1.0).contains(&r)
47		&& (0.0 ..= 1.0).contains(&g)
48		&& (0.0 ..= 1.0).contains(&b)
49}
50
51fn path_to_cstr<P>(path: P) -> CString
52where
53	P: AsRef<Path>
54{
55	let bytes = path.as_ref().as_os_str().as_bytes().to_vec();
56	CString::new(bytes).expect("path must not contain nul")
57}
58
59/// The size type used by lottie [`Animation`].
60#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
61#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
62pub struct Size {
63	pub width: usize,
64	pub height: usize
65}
66
67impl Size {
68	pub const fn new(width: usize, height: usize) -> Self {
69		Self { width, height }
70	}
71}
72
73/// It is very important that [`Bgra`] and `u32` have exactly the same size. This
74/// mod does nothing other than fail to compile if that was not the case.
75#[allow(dead_code)]
76mod bgra8_size {
77	use super::Bgra;
78	use std::{marker::PhantomData, mem};
79
80	#[derive(Default)]
81	struct AssertSize<const N: usize>(PhantomData<[(); N]>);
82
83	impl<const N: usize> AssertSize<N> {
84		const fn new() -> Self {
85			Self(PhantomData)
86		}
87	}
88
89	impl AssertSize<4> {
90		const fn assert_size_u32(self) {}
91	}
92
93	const _: () = {
94		AssertSize::<{ mem::size_of::<Bgra>() }>::new().assert_size_u32();
95		AssertSize::<{ mem::size_of::<u32>() }>::new().assert_size_u32();
96	};
97}
98
99/// A surface has a fixed size and contains pixel data for it. You can render frames onto
100/// the surface.
101pub struct Surface {
102	data: Vec<Bgra>,
103	size: Size
104}
105
106impl Surface {
107	/// Create a new surface with a fixed size.
108	pub fn new(size: Size) -> Self {
109		Self {
110			data: Vec::with_capacity(size.width * size.height),
111			size
112		}
113	}
114
115	/// Return the size of the surface.
116	pub fn size(&self) -> Size {
117		self.size
118	}
119
120	/// Return the width of the surface.
121	pub fn width(&self) -> usize {
122		self.size.width
123	}
124
125	/// Return the height of the surface.
126	pub fn height(&self) -> usize {
127		self.size.height
128	}
129
130	/// Return the pixel data of the surface.
131	pub fn data(&self) -> &[Bgra] {
132		&self.data
133	}
134
135	/// Return the pixel data of the surface. You should prefer [`data()`] unless you
136	/// absolutely need owned access to the data.
137	pub fn into_data(self) -> Vec<Bgra> {
138		self.data
139	}
140
141	/// Return the raw pixel data of the surface.
142	pub fn data_as_bytes(&self) -> &[u8] {
143		// Safety: We are not mutating the surface data for the lifetime of the returned
144		// slice, and the memory was properly allocated by Vec, so this is fine.
145		unsafe {
146			slice::from_raw_parts(
147				self.data.as_ptr() as *const u8,
148				self.data.len() * mem::size_of::<Bgra>()
149			)
150		}
151	}
152
153	/// Returns an iterator over the pixels of the surface.
154	pub fn pixels(&self) -> impl Iterator<Item = (usize, usize, Bgra)> {
155		let width = self.width();
156		self.data().iter().enumerate().map(move |(i, color)| {
157			let x = i % width;
158			let y = i / width;
159			(x, y, *color)
160		})
161	}
162
163	/// Return a pointer to the pixel data.
164	fn as_mut_ptr(&mut self) -> *mut u32 {
165		self.data.as_mut_ptr() as *mut u32
166	}
167
168	/// Set the length of the pixel data to `width * height`.
169	unsafe fn set_len(&mut self) {
170		unsafe { self.data.set_len(self.width() * self.height()) }
171	}
172}
173
174impl AsRef<[u8]> for Surface {
175	fn as_ref(&self) -> &[u8] {
176		self.data_as_bytes()
177	}
178}
179
180/// A lottie animation.
181pub struct Animation(NonNull<Lottie_Animation_S>);
182
183impl Debug for Animation {
184	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185		f.debug_struct("Animation").finish_non_exhaustive()
186	}
187}
188
189impl Drop for Animation {
190	fn drop(&mut self) {
191		unsafe {
192			lottie_animation_destroy(self.0.as_ptr());
193		}
194	}
195}
196
197impl Animation {
198	fn from_ptr(ptr: *mut Lottie_Animation_S) -> Option<Self> {
199		(!ptr.is_null()).then(|| {
200			// Safety: This is only called if ptr is non null
201			Self(unsafe { NonNull::new_unchecked(ptr) })
202		})
203	}
204
205	/// Constructs an animation object from file path. This file needs to be in JSON
206	/// format; if you want to read telegram's tgs files, you need to decompress
207	/// them first.
208	///
209	/// Note that the rlottie library might cache the file and/or its resources.
210	pub fn from_file<P>(path: P) -> Option<Self>
211	where
212		P: AsRef<Path>
213	{
214		let path = path_to_cstr(path);
215		let ptr = unsafe { lottie_animation_from_file(path.as_ptr()) };
216		Self::from_ptr(ptr)
217	}
218
219	/// Constructs an animation object from JSON string data. External resources are
220	/// resolved relative to `resource_path`.
221	///
222	/// Note that the `cache_key` might be used by the rlottie library to cache the
223	/// json data and/or its resources.
224	///
225	/// This method will panic if json_data or cache_key contain nul bytes.
226	pub fn from_data<D, K, P>(
227		json_data: D,
228		cache_key: K,
229		resource_path: P
230	) -> Option<Self>
231	where
232		D: Into<Vec<u8>>,
233		K: Into<Vec<u8>>,
234		P: AsRef<Path>
235	{
236		let json_data =
237			CString::new(json_data).expect("json_data must not contain nul");
238		let cache_key = CString::new(cache_key).expect("key must not contain nul");
239		let resource_path = path_to_cstr(resource_path);
240		let ptr = unsafe {
241			lottie_animation_from_data(
242				json_data.as_ptr(),
243				cache_key.as_ptr(),
244				resource_path.as_ptr()
245			)
246		};
247		Self::from_ptr(ptr)
248	}
249
250	/// Return the default viewport size of this animation.
251	pub fn size(&self) -> Size {
252		let mut size = Size {
253			width: 0,
254			height: 0
255		};
256		unsafe {
257			lottie_animation_get_size(
258				self.0.as_ptr(),
259				&mut size.width,
260				&mut size.height
261			);
262		}
263		size
264	}
265
266	/// Return the total duration of this animation in seconds.
267	pub fn duration(&self) -> f64 {
268		unsafe { lottie_animation_get_duration(self.0.as_ptr()) }
269	}
270
271	/// Return the total number of frames in this animation.
272	pub fn totalframe(&self) -> usize {
273		unsafe { lottie_animation_get_totalframe(self.0.as_ptr()) }
274	}
275
276	/// Return the default framerate of this animation.
277	pub fn framerate(&self) -> f64 {
278		unsafe { lottie_animation_get_framerate(self.0.as_ptr()) }
279	}
280
281	/// Maps position to frame number and returns it.
282	pub fn frame_at_pos(&self, pos: f32) -> usize {
283		unsafe { lottie_animation_get_frame_at_pos(self.0.as_ptr(), pos) }
284	}
285
286	/// Render the contents of a frame onto the surface.
287	pub fn render(&mut self, frame_num: usize, surface: &mut Surface) {
288		unsafe {
289			lottie_animation_render(
290				self.0.as_ptr(),
291				frame_num,
292				surface.as_mut_ptr(),
293				surface.width(),
294				surface.height(),
295				surface.width() * 4
296			);
297			surface.set_len();
298		}
299	}
300
301	pub fn set_fill_color(&mut self, keypath: &str, color: Rgb) {
302		assert!(color_is_valid(color), "color is not valid");
303		let keypath = CString::new(keypath).unwrap();
304		let RGB { r, g, b } = color;
305		unsafe {
306			lottie_animation_property_override(
307                self.0.as_ptr(),
308                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_FILLCOLOR,
309                keypath.as_ptr(),
310                r,
311                g,
312                b,
313            );
314		}
315	}
316
317	pub fn set_fill_opacity(&mut self, keypath: &str, opacity: f64) {
318		assert!(
319			(0.0 ..= 100.0).contains(&opacity),
320			"opacity values must be between 0.0 and 100.0"
321		);
322		let keypath = CString::new(keypath).unwrap();
323		unsafe {
324			lottie_animation_property_override(
325                self.0.as_ptr(),
326                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_FILLOPACITY,
327                keypath.as_ptr(),
328                opacity,
329            );
330		}
331	}
332
333	pub fn set_stroke_color(&mut self, keypath: &str, color: Rgb) {
334		assert!(color_is_valid(color), "color is not valid");
335		let keypath = CString::new(keypath).unwrap();
336		let RGB { r, g, b } = color;
337		unsafe {
338			lottie_animation_property_override(
339                self.0.as_ptr(),
340                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_STROKECOLOR,
341                keypath.as_ptr(),
342                r,
343                g,
344                b,
345            );
346		}
347	}
348
349	pub fn set_stroke_opacity(&mut self, keypath: &str, opacity: f64) {
350		assert!(
351			(0.0 ..= 100.0).contains(&opacity),
352			"opacity values must be between 0.0 and 100.0"
353		);
354		let keypath = CString::new(keypath).unwrap();
355		unsafe {
356			lottie_animation_property_override(
357                self.0.as_ptr(),
358                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY,
359                keypath.as_ptr(),
360                opacity,
361            );
362		}
363	}
364
365	pub fn set_stroke_width(&mut self, keypath: &str, width: f64) {
366		let keypath = CString::new(keypath).unwrap();
367		unsafe {
368			lottie_animation_property_override(
369                self.0.as_ptr(),
370                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH,
371                keypath.as_ptr(),
372                width,
373            );
374		}
375	}
376
377	pub fn set_tr_position(&mut self, keypath: &str, x: f64, y: f64) {
378		let keypath = CString::new(keypath).unwrap();
379		unsafe {
380			lottie_animation_property_override(
381                self.0.as_ptr(),
382                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_TR_POSITION,
383                keypath.as_ptr(),
384                x,
385                y,
386            );
387		}
388	}
389
390	pub fn set_tr_scale(&mut self, keypath: &str, width: f64, height: f64) {
391		let keypath = CString::new(keypath).unwrap();
392		unsafe {
393			lottie_animation_property_override(
394                self.0.as_ptr(),
395                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_TR_SCALE,
396                keypath.as_ptr(),
397                width,
398                height,
399            );
400		}
401	}
402
403	pub fn set_tr_rotation(&mut self, keypath: &str, rotation: f64) {
404		let keypath = CString::new(keypath).unwrap();
405		unsafe {
406			lottie_animation_property_override(
407                self.0.as_ptr(),
408                rlottie_sys::Lottie_Animation_Property::LOTTIE_ANIMATION_PROPERTY_TR_ROTATION,
409                keypath.as_ptr(),
410                rotation,
411            );
412		}
413	}
414}