use std::sync::OnceLock;
use image::error::ImageResult;
use image::{Frame, ImageError};
use image::{GenericImageView, Rgba, RgbaImage};
use image::Delay;
use image::imageops::overlay;
use image::imageops::resize;
pub use image::imageops::FilterType;
const FRAMES: usize = 10;
const FRAME_DELAY: u32 = 20;
pub const HAND_HEIGHT_WIDTH: u32 = 112;
static HANDS_RGBA: OnceLock<[RgbaImage; 5]> = OnceLock::new();
#[cfg(not(feature = "bundle_raw_hands"))]
fn load_hand_webp(buf: &[u8]) -> Result<RgbaImage, ImageError> {
use image::load_from_memory_with_format;
let dyn_image = load_from_memory_with_format(buf, image::ImageFormat::WebP)?;
Ok(dyn_image.to_rgba8())
}
#[cfg(feature = "bundle_raw_hands")]
fn load_hand_raw(buf: &[u8]) -> Result<RgbaImage, ImageError> {
use image::error::DecodingError;
let image = RgbaImage::from_raw(HAND_HEIGHT_WIDTH, HAND_HEIGHT_WIDTH, buf.to_vec()).ok_or(
ImageError::Decoding(DecodingError::from_format_hint(
image::error::ImageFormatHint::Unknown,
)),
)?;
Ok(image)
}
pub fn generate(
image: RgbaImage,
filter: FilterType,
delay_ms: Option<u32>,
) -> ImageResult<impl IntoIterator<Item = Frame>> {
let hands = HANDS_RGBA.get_or_init(|| {
#[cfg(not(feature = "bundle_raw_hands"))]
{
use hands::HANDS_WEBP;
return HANDS_WEBP.map(|img| load_hand_webp(img).unwrap());
}
#[cfg(feature = "bundle_raw_hands")]
{
use hands::HANDS_RAW;
return HANDS_RAW.map(|img| load_hand_raw(img).unwrap());
}
});
Ok((0..FRAMES).map(move |num| encode_single_frame(num, delay_ms, &image, filter, hands)))
}
pub fn encode_single_frame(
num: usize,
delay_ms: Option<u32>,
image: &RgbaImage,
filter: FilterType,
hands: &[impl GenericImageView<Pixel = Rgba<u8>>; FRAMES / 2],
) -> image::Frame {
let squeeze = if num < FRAMES / 2 { num } else { FRAMES - num } as f64;
let width_scale = 0.8 + squeeze * 0.02;
let height_scale = 0.8 - squeeze * 0.05;
let width = (width_scale * HAND_HEIGHT_WIDTH as f64) as u32;
let height = (height_scale * HAND_HEIGHT_WIDTH as f64) as u32;
let offset_x = (((1.0 - width_scale) * 0.5 + 0.1) * HAND_HEIGHT_WIDTH as f64) as i64;
let offset_y = (((1.0 - height_scale) - 0.08) * HAND_HEIGHT_WIDTH as f64) as i64;
let resized_background = resize(image, width, height, filter);
let mut composited_image = RgbaImage::new(HAND_HEIGHT_WIDTH, HAND_HEIGHT_WIDTH);
overlay(
&mut composited_image,
&resized_background,
offset_x,
offset_y,
);
let hand_overlay = &hands[num as usize / 2];
overlay(&mut composited_image, hand_overlay, 0, 0);
let frame_with_delay = Frame::from_parts(
composited_image,
0,
0,
Delay::from_numer_denom_ms(delay_ms.unwrap_or(FRAME_DELAY), 1),
);
frame_with_delay
}
#[cfg(feature = "encode_to_gif")]
mod encode_gif;
#[cfg(feature = "encode_to_gif")]
pub use encode_gif::encode_gif;
#[cfg(feature = "encode_to_apng")]
mod encode_apng;
#[cfg(feature = "encode_to_apng")]
pub use apng::image_png as png;
#[cfg(feature = "encode_to_apng")]
pub use encode_apng::encode_apng;
mod hands;
#[cfg(feature = "image")]
pub use image;