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