diff --git a/README.md b/README.md index a3212bd..80378b1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # cowmic -generates memes \ No newline at end of file +generates memes + +`mogrify -define png:format=png32 -format png *.png` diff --git a/cowgen/Cargo.toml b/cowgen/Cargo.toml index e6a101d..463069b 100644 --- a/cowgen/Cargo.toml +++ b/cowgen/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ril = { version = "0", features = ["all"] } +magick_rust = { version = "0.16" } diff --git a/cowgen/src/lib.rs b/cowgen/src/lib.rs index 1b4a90c..eb56ff6 100644 --- a/cowgen/src/lib.rs +++ b/cowgen/src/lib.rs @@ -1,3 +1,146 @@ +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, V: AsRef> { + elements: Vec>, + base: Image, +} + +impl, V: AsRef> Template { + pub fn new(base: Vec, elements: Vec>) -> Result { + Ok(Self { + base: match Image::::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, 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::::new(); + base_image.encode(ImageFormat::Png, &mut buf).unwrap(); + + Ok(buf) + } +} + +pub struct Element, V: AsRef> { + position: (i32, i32), + dimensions: (u32, u32), + media: Media, +} + +impl, V: AsRef> Element { + pub fn new(media: Media, position: (i32, i32), dimensions: (u32, u32)) -> Self { + Self { + position, + dimensions, + media, + } + } + + fn into_image(self) -> Result { + 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, V: AsRef> { + Text(Text), + Image(Vec), +} + +pub struct Text, V: AsRef> { + text: T, + font: V, + size: f32, + fill: (u8, u8, u8, u8), +} + +impl, V: AsRef> Text { + 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 for CowError { + fn from(r: ril::Error) -> Self { + CowError::Other(r.to_string()) + } +} + +impl From for CowError { + fn from(m: MagickError) -> Self { + CowError::Magick(m.0.to_owned()) + } +} + #[cfg(test)] mod tests { #[test] diff --git a/cowmic/src/cow.png b/cowmic/src/cow.png new file mode 100644 index 0000000..acf90ad Binary files /dev/null and b/cowmic/src/cow.png differ diff --git a/cowmic/src/cow2.png b/cowmic/src/cow2.png new file mode 100644 index 0000000..8096e53 Binary files /dev/null and b/cowmic/src/cow2.png differ diff --git a/cowmic/src/main.rs b/cowmic/src/main.rs index e7a11a9..ec49f0a 100644 --- a/cowmic/src/main.rs +++ b/cowmic/src/main.rs @@ -1,3 +1,31 @@ -fn main() { - println!("Hello, world!"); +use std::{fs::File, io::Write}; + +use cowgen::{CowError, Element, Text}; + +fn main() -> Result<(), CowError> { + let out = cowgen::Template::new( + include_bytes!("cow.png").to_vec(), + vec![ + Element::new( + cowgen::Media::Text(Text::new( + "Hello", + "/usr/share/fonts/TTF/OpenSans-ExtraBold.ttf", + 20.0, + (0, 0, 0, 128), + )), + (0, 0), + (64, 64), + ), + Element::new( + cowgen::Media::Image(include_bytes!("mariah.jpg").to_vec()), + (200, 400), + (800, 200), + ), + ], + )? + .produce()?; + let mut f = File::create("out2.png").unwrap(); + f.write_all(&out).unwrap(); + f.flush().unwrap(); + Ok(()) }