1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(rust_2018_idioms)]
3#![deny(elided_lifetimes_in_paths, unreachable_pub)]
4
5use 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#[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#[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
99pub struct Surface {
102 data: Vec<Bgra>,
103 size: Size
104}
105
106impl Surface {
107 pub fn new(size: Size) -> Self {
109 Self {
110 data: Vec::with_capacity(size.width * size.height),
111 size
112 }
113 }
114
115 pub fn size(&self) -> Size {
117 self.size
118 }
119
120 pub fn width(&self) -> usize {
122 self.size.width
123 }
124
125 pub fn height(&self) -> usize {
127 self.size.height
128 }
129
130 pub fn data(&self) -> &[Bgra] {
132 &self.data
133 }
134
135 pub fn into_data(self) -> Vec<Bgra> {
138 self.data
139 }
140
141 pub fn data_as_bytes(&self) -> &[u8] {
143 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 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 fn as_mut_ptr(&mut self) -> *mut u32 {
165 self.data.as_mut_ptr() as *mut u32
166 }
167
168 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
180pub 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 Self(unsafe { NonNull::new_unchecked(ptr) })
202 })
203 }
204
205 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 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 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 pub fn duration(&self) -> f64 {
268 unsafe { lottie_animation_get_duration(self.0.as_ptr()) }
269 }
270
271 pub fn totalframe(&self) -> usize {
273 unsafe { lottie_animation_get_totalframe(self.0.as_ptr()) }
274 }
275
276 pub fn framerate(&self) -> f64 {
278 unsafe { lottie_animation_get_framerate(self.0.as_ptr()) }
279 }
280
281 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 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}