Compare commits
5 Commits
1442d3821a
...
e7e5236b80
Author | SHA1 | Date |
---|---|---|
emilis | e7e5236b80 | |
emilis | 18938dd2a0 | |
emilis | d4946a29d1 | |
emilis | 2b6a3f24ad | |
cel 🌸 | 9d70f1598d |
|
@ -1,3 +1,5 @@
|
|||
# cowmic
|
||||
|
||||
generates memes
|
||||
generates memes
|
||||
|
||||
`mogrify -define png:format=png32 -format png *.png`
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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<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]
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue