use crate::pixel::Pixel;
use image::{GenericImageView, ImageFormat};
use mlua::{AnyUserData, Error, UserData, UserDataMethods};
pub struct DynamicImage {
pub delegate: image::DynamicImage,
}
impl UserData for DynamicImage {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_function("adjust_contrast", |_lua, (ud, c): (AnyUserData, f32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.adjust_contrast(c),
})
});
methods.add_function("blur", |_lua, (ud, sigma): (AnyUserData, f32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.blur(sigma),
})
});
methods.add_function("brighten", |_lua, (ud, value): (AnyUserData, i32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.brighten(value),
})
});
methods.add_method("clone", |_lua, this, ()| {
Ok(DynamicImage {
delegate: this.delegate.clone(),
})
});
methods.add_method("color", |_lua, this, ()| {
let color = this.delegate.color();
let color_string = format!("{:?}", color);
Ok(color_string.to_lowercase())
});
methods.add_function(
"crop",
|_lua, (ud, x, y, width, height): (AnyUserData, u32, u32, u32, u32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.clone().crop(x, y, width, height),
})
},
);
methods.add_function(
"crop_imm",
|_lua, (ud, x, y, width, height): (AnyUserData, u32, u32, u32, u32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.crop_imm(x, y, width, height),
})
},
);
methods.add_method("dimensions", |_lua, this, ()| {
let (width, height) = this.delegate.dimensions();
Ok((width, height))
});
methods.add_function("eq", |_lua, (ud, other_ud): (AnyUserData, AnyUserData)| {
let this = ud.borrow::<Self>()?;
let other = other_ud.borrow::<Self>()?;
let equal = this.delegate.eq(&other.delegate);
Ok(equal)
});
methods.add_function("fliph", |_lua, ud: AnyUserData| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.fliph(),
})
});
methods.add_function("flipv", |_lua, ud: AnyUserData| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.flipv(),
})
});
methods.add_function("get_pixel", |_lua, (ud, x, y): (AnyUserData, u32, u32)| {
let this = ud.borrow::<Self>()?;
let pixel = this.delegate.get_pixel(x - 1, y - 1);
Ok(Pixel { delegate: pixel })
});
methods.add_method("grayscale", |_lua, this, ()| {
Ok(DynamicImage {
delegate: this.delegate.grayscale(),
})
});
methods.add_method("height", |_lua, this, ()| Ok(this.delegate.height()));
methods.add_function("huerotate", |_lua, (ud, value): (AnyUserData, i32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.huerotate(value),
})
});
methods.add_function("in_bounds", |_lua, (ud, x, y): (AnyUserData, u32, u32)| {
let this = ud.borrow::<Self>()?;
let in_bounds = this.delegate.in_bounds(x, y);
Ok(in_bounds)
});
methods.add_method("into_bytes", |_lua, this, ()| Ok(this.delegate.clone().into_bytes()));
methods.add_function("invert", |_lua, ud: AnyUserData| {
let mut this = ud.borrow_mut::<Self>()?;
this.delegate.invert();
Ok(())
});
methods.add_function(
"resize",
|_lua, (ud, nwidth, nheight, filter_string): (AnyUserData, u32, u32, String)| {
let filter = crate::ops::filter_type::parse(filter_string.as_str())?;
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.resize(nwidth, nheight, filter),
})
},
);
methods.add_function(
"resize_exact",
|_lua, (ud, nwidth, nheight, filter_string): (AnyUserData, u32, u32, String)| {
let filter = crate::ops::filter_type::parse(filter_string.as_str())?;
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.resize_exact(nwidth, nheight, filter),
})
},
);
methods.add_function(
"resize_to_fill",
|_lua, (ud, nwidth, nheight, filter_string): (AnyUserData, u32, u32, String)| {
let filter = crate::ops::filter_type::parse(filter_string.as_str())?;
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.resize_to_fill(nwidth, nheight, filter),
})
},
);
methods.add_method("rotate90", |_lua, this, ()| {
Ok(DynamicImage {
delegate: this.delegate.rotate90(),
})
});
methods.add_method("rotate180", |_lua, this, ()| {
Ok(DynamicImage {
delegate: this.delegate.rotate180(),
})
});
methods.add_method("rotate270", |_lua, this, ()| {
Ok(DynamicImage {
delegate: this.delegate.rotate270(),
})
});
methods.add_method("save", |_lua, this, path: String| {
this.delegate
.save(path)
.map_err(|err| Error::RuntimeError(err.to_string()))
});
methods.add_function(
"save_with_format",
|_lua, (ud, path, format_str): (AnyUserData, String, String)| {
let this = ud.borrow::<Self>()?;
let format: ImageFormat = match format_str.as_str() {
"Avif" | "avif" => ImageFormat::Avif,
"Bmp" | "bmp" => ImageFormat::Bmp,
"Dds" | "dds" => ImageFormat::Dds,
"Farbeld" | "farbeld" => ImageFormat::Farbfeld,
"Gif" | "gif" => ImageFormat::Gif,
"Hdr" | "hdr" => ImageFormat::Hdr,
"Ico" | "ico" => ImageFormat::Ico,
"Jpeg" | "jpeg" => ImageFormat::Jpeg,
"OpenExr" | "openexr" => ImageFormat::OpenExr,
"Png" | "png" => ImageFormat::Png,
"Pnm" | "pnm" => ImageFormat::Pnm,
"Qoi" | "qoi" => ImageFormat::Qoi,
"Tga" | "tga" => ImageFormat::Tga,
"Tiff" | "tiff" => ImageFormat::Tiff,
"WebP" | "webp" => ImageFormat::WebP,
_ => return Err(Error::RuntimeError("Unknown format".to_string())),
};
this.delegate
.save_with_format(path, format)
.map_err(|err| Error::RuntimeError(err.to_string()))
},
);
methods.add_function("thumbnail", |_lua, (ud, nwidth, nheight): (AnyUserData, u32, u32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.thumbnail(nwidth, nheight),
})
});
methods.add_function(
"thumbnail_exact",
|_lua, (ud, nwidth, nheight): (AnyUserData, u32, u32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.thumbnail_exact(nwidth, nheight),
})
},
);
methods.add_method("to_luma8", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_luma8()),
})
});
methods.add_method("to_luma16", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_luma16()),
})
});
methods.add_method("to_luma32f", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_luma32f()),
})
});
methods.add_method("to_luma_alpha8", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_luma_alpha8()),
})
});
methods.add_method("to_luma_alpha16", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_luma_alpha16()),
})
});
methods.add_method("to_luma_alpha32f", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_luma_alpha32f()),
})
});
methods.add_method("to_rgb8", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_rgb8()),
})
});
methods.add_method("to_rgb16", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_rgb16()),
})
});
methods.add_method("to_rgb32f", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_rgb32f()),
})
});
methods.add_method("to_rgba8", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_rgba8()),
})
});
methods.add_method("to_rgba16", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_rgba16()),
})
});
methods.add_method("to_rgba32f", |_lua, this, ()| {
Ok(DynamicImage {
delegate: image::DynamicImage::from(this.delegate.to_rgba32f()),
})
});
methods.add_function("unsharpen", |_lua, (ud, sigma, threshold): (AnyUserData, f32, i32)| {
let this = ud.borrow::<Self>()?;
Ok(DynamicImage {
delegate: this.delegate.unsharpen(sigma, threshold),
})
});
methods.add_method("width", |_lua, this, ()| Ok(this.delegate.width()));
}
}
#[cfg(test)]
mod tests {
use mlua::Lua;
use std::error::Error;
use std::path::Path;
#[test]
fn adjust_contrast() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local adjusted = img:adjust_contrast(0.1)
assert(img:eq(adjusted) == false)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn blur() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local blurred = img:blur(0.5)
assert(img:eq(blurred) == false)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn brighten() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local brightened = img:brighten(10)
assert(img:eq(brightened) == false)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn clone() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local cloned = img:clone()
assert(img:eq(cloned) == true)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn color() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
return img:color()
"#;
let color: String = lua.load(script).eval()?;
assert_eq!(color, "rgb8");
Ok(())
}
#[test]
fn crop() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local cropped = img:crop(10, 5, 40, 30)
return cropped:width(), cropped:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 40);
assert_eq!(height, 30);
Ok(())
}
#[test]
fn crop_imm() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local cropped = img:crop_imm(10, 5, 40, 30)
return cropped:width(), cropped:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 40);
assert_eq!(height, 30);
Ok(())
}
#[test]
fn dimensions() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
return img:dimensions()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 800);
assert_eq!(height, 800);
Ok(())
}
#[test]
fn fliph() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:fliph()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn flipv() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:flipv()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn get_pixel() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
return tostring(img:get_pixel(10, 10))
"#;
let pixel_formatted: String = lua.load(script).eval()?;
assert_eq!(pixel_formatted, "Rgba([2, 0, 2, 255])");
Ok(())
}
#[test]
fn grayscale() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local grayscale = img:grayscale()
assert(img:eq(grayscale) == false)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn huerotate() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local huerotated = img:huerotate(23)
assert(img:eq(huerotated) == false)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn in_bounds() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
return img:in_bounds(5, 5), img:in_bounds(900, 900)
"#;
let (result1, result2): (bool, bool) = lua.load(script).eval()?;
assert_eq!(result1, true);
assert_eq!(result2, false);
Ok(())
}
#[test]
fn into_bytes() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
return img:into_bytes()
"#;
let bytes: Vec<u8> = lua.load(script).eval()?;
assert_eq!(bytes.len(), 1920000);
Ok(())
}
#[test]
fn resize() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local resized = img:resize(40, 40, 'gaussian')
return resized:width(), resized:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 40);
assert_eq!(height, 40);
Ok(())
}
#[test]
fn resize_exact() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local resized = img:resize_exact(40, 30, 'gaussian')
return resized:width(), resized:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 40);
assert_eq!(height, 30);
Ok(())
}
#[test]
fn resize_to_fill() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local resized = img:resize_to_fill(40, 40, 'gaussian')
return resized:width(), resized:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 40);
assert_eq!(height, 40);
Ok(())
}
#[test]
fn rotates() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local rotated = img:rotate90()
assert(img:eq(rotated) == false)
rotated = rotated:rotate90()
rotated = rotated:rotate90()
rotated = rotated:rotate90()
assert(img:eq(rotated))
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn save() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local tmpname = os.tmpname() .. '.png'
img:save(tmpname)
return tmpname
"#;
let tmpname: String = lua.load(script).eval()?;
assert!(Path::new(tmpname.as_str()).exists());
Ok(())
}
#[test]
fn save_with_format() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local tmpname = os.tmpname() .. '.gif'
img:save_with_format(tmpname, 'gif')
return tmpname
"#;
let tmpname: String = lua.load(script).eval()?;
assert!(Path::new(tmpname.as_str()).exists());
Ok(())
}
#[test]
fn thumbnail() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local resized = img:thumbnail(40, 30)
return resized:width(), resized:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 30);
assert_eq!(height, 30);
Ok(())
}
#[test]
fn thumbnail_exact() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local resized = img:thumbnail_exact(40, 30)
return resized:width(), resized:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 40);
assert_eq!(height, 30);
Ok(())
}
#[test]
fn to_luma8() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_luma8()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_luma16() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_luma16()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_luma32f() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_luma32f()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_luma_alpha8() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_luma_alpha8()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_luma_alpha16() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_luma_alpha16()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_luma_alpha32f() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_luma_alpha32f()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_rgb8() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_rgb8()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_rgb16() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_rgb16()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_rgb32f() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_rgb32f()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_rgba8() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_rgba8()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_rgba16() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_rgba16()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn to_rgba32f() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
img:to_rgba32f()
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn unsharpen() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
local blurred = img:unsharpen(0.1, 1000)
"#;
lua.load(script).exec()?;
Ok(())
}
#[test]
fn width_and_height() -> Result<(), Box<dyn Error>> {
let lua = Lua::new();
crate::preload(&lua)?;
let script = r#"
local image = require('image')
local img = image.open('testdata/fractal.png')
return img:width(), img:height()
"#;
let (width, height): (u32, u32) = lua.load(script).eval()?;
assert_eq!(width, 800);
assert_eq!(height, 800);
Ok(())
}
}