cowmic/cowgen/src/lib.rs

152 lines
4.1 KiB
Rust

use magick_rust::{magick_wand_genesis, MagickError, MagickWand};
use ril::prelude::*;
use std::sync::Once;
use std::{fs::File, path::Path};
// Used to make sure MagickWand is initialized exactly once. Note that we
// do not bother shutting down, we simply exit when we're done.
static START: Once = Once::new();
pub struct Template<T: AsRef<str>, V: AsRef<Path>> {
elements: Vec<Element<T, V>>,
base: Image<Rgba>,
}
impl<T: AsRef<str>, V: AsRef<Path>> Template<T, V> {
pub fn new(base: Vec<u8>, elements: Vec<Element<T, V>>) -> Result<Self, CowError> {
Ok(Self {
base: match Image::<Rgba>::from_bytes_inferred(&base) {
Ok(img) => img,
_ => {
START.call_once(|| {
magick_wand_genesis();
});
let mut wand = MagickWand::new();
wand.read_image_blob(&base)?;
wand.set_format("png32")?;
let base = wand.write_image_blob("png")?;
Image::from_bytes_inferred(base)?
}
},
elements,
})
}
pub fn produce(self) -> Result<Vec<u8>, CowError> {
let size = self.base.dimensions();
let mut base_image = self.base.with_overlay_mode(OverlayMode::Merge);
for element in self.elements {
let mut pos = element.position;
let mut img: Image = element.into_image()?;
if pos.0 < 0 {
img.crop(-pos.0 as u32, 0, size.0, size.1);
pos.0 = 0;
}
if pos.1 < 0 {
img.crop(0, -pos.1 as u32, size.0, size.1);
pos.1 = 0;
}
base_image.draw(&Paste::new(img.convert()).with_position(pos.0 as u32, pos.1 as u32));
}
let mut buf = Vec::<u8>::new();
base_image.encode(ImageFormat::Png, &mut buf).unwrap();
Ok(buf)
}
}
pub struct Element<T: AsRef<str>, V: AsRef<Path>> {
position: (i32, i32),
dimensions: (u32, u32),
media: Media<T, V>,
}
impl<T: AsRef<str>, V: AsRef<Path>> Element<T, V> {
pub fn new(media: Media<T, V>, position: (i32, i32), dimensions: (u32, u32)) -> Self {
Self {
position,
dimensions,
media,
}
}
fn into_image(self) -> Result<Image, CowError> {
match self.media {
Media::Image(image) => Ok(Image::from_bytes_inferred(image)?.resized(
self.dimensions.0,
self.dimensions.1,
ResizeAlgorithm::Bicubic,
)),
Media::Text(text) => Ok(Image::new(
self.dimensions.0,
self.dimensions.1,
Dynamic::Rgba(Rgba::transparent()),
)
.with(
&TextSegment::new(
&Font::from_reader(File::open(text.font).unwrap(), 12.0).unwrap(),
text.text,
Dynamic::Rgba(Rgba::new(
text.fill.0,
text.fill.1,
text.fill.2,
text.fill.3,
)),
)
.with_size(text.size),
)),
}
}
}
pub enum Media<T: AsRef<str>, V: AsRef<Path>> {
Text(Text<T, V>),
Image(Vec<u8>),
}
pub struct Text<T: AsRef<str>, V: AsRef<Path>> {
text: T,
font: V,
size: f32,
fill: (u8, u8, u8, u8),
}
impl<T: AsRef<str>, V: AsRef<Path>> Text<T, V> {
pub fn new(text: T, font: V, size: f32, fill: (u8, u8, u8, u8)) -> Self {
Self {
text,
font,
size,
fill,
}
}
}
#[derive(Clone, Debug)]
pub enum CowError {
Magick(String),
Other(String),
}
impl From<ril::Error> for CowError {
fn from(r: ril::Error) -> Self {
CowError::Other(r.to_string())
}
}
impl From<MagickError> for CowError {
fn from(m: MagickError) -> Self {
CowError::Magick(m.0.to_owned())
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}