Fix edge cases and consistency issues
This commit is contained in:
@@ -19,8 +19,8 @@ pub fn calculate_position(
|
||||
|
||||
let center_x = iw.saturating_sub(ww) / 2;
|
||||
let center_y = ih.saturating_sub(wh) / 2;
|
||||
let right_x = iw.saturating_sub(ww + margin);
|
||||
let bottom_y = ih.saturating_sub(wh + margin);
|
||||
let right_x = iw.saturating_sub(ww).saturating_sub(margin);
|
||||
let bottom_y = ih.saturating_sub(wh).saturating_sub(margin);
|
||||
|
||||
match position {
|
||||
WatermarkPosition::TopLeft => (margin, margin),
|
||||
@@ -69,7 +69,7 @@ pub fn apply_watermark(
|
||||
if *tiled {
|
||||
apply_tiled_image_watermark(img, path, *opacity, *scale, *rotation, *margin)
|
||||
} else {
|
||||
apply_image_watermark(img, path, *position, *opacity, *scale, *rotation)
|
||||
apply_image_watermark(img, path, *position, *opacity, *scale, *rotation, *margin)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,20 +128,29 @@ fn find_system_font(family: Option<&str>) -> Result<Vec<u8>> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Recursively walk a directory and collect file paths
|
||||
/// Recursively walk a directory and collect file paths (max depth 5)
|
||||
fn walkdir(dir: &std::path::Path) -> std::io::Result<Vec<std::path::PathBuf>> {
|
||||
walkdir_depth(dir, 5)
|
||||
}
|
||||
|
||||
fn walkdir_depth(dir: &std::path::Path, max_depth: u32) -> std::io::Result<Vec<std::path::PathBuf>> {
|
||||
const MAX_RESULTS: usize = 10_000;
|
||||
let mut results = Vec::new();
|
||||
if dir.is_dir() {
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Ok(sub) = walkdir(&path) {
|
||||
results.extend(sub);
|
||||
}
|
||||
} else {
|
||||
results.push(path);
|
||||
if max_depth == 0 || !dir.is_dir() {
|
||||
return Ok(results);
|
||||
}
|
||||
for entry in std::fs::read_dir(dir)? {
|
||||
if results.len() >= MAX_RESULTS {
|
||||
break;
|
||||
}
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Ok(sub) = walkdir_depth(&path, max_depth - 1) {
|
||||
results.extend(sub);
|
||||
}
|
||||
} else {
|
||||
results.push(path);
|
||||
}
|
||||
}
|
||||
Ok(results)
|
||||
@@ -156,8 +165,8 @@ fn render_text_to_image(
|
||||
opacity: f32,
|
||||
) -> image::RgbaImage {
|
||||
let scale = ab_glyph::PxScale::from(font_size);
|
||||
let text_width = (text.len() as f32 * font_size * 0.6) as u32 + 4;
|
||||
let text_height = (font_size * 1.4) as u32 + 4;
|
||||
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as u32).saturating_add(4).min(8192);
|
||||
let text_height = ((font_size.min(1000.0) * 1.4) as u32).saturating_add(4).min(4096);
|
||||
|
||||
let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
|
||||
let draw_color = Rgba([color[0], color[1], color[2], alpha]);
|
||||
@@ -167,6 +176,30 @@ fn render_text_to_image(
|
||||
buf
|
||||
}
|
||||
|
||||
/// Expand canvas so rotated content fits without clipping, then rotate
|
||||
fn rotate_on_expanded_canvas(img: &image::RgbaImage, radians: f32) -> DynamicImage {
|
||||
let w = img.width() as f32;
|
||||
let h = img.height() as f32;
|
||||
let cos = radians.abs().cos();
|
||||
let sin = radians.abs().sin();
|
||||
// Bounding box of rotated rectangle
|
||||
let new_w = (w * cos + h * sin).ceil() as u32 + 2;
|
||||
let new_h = (w * sin + h * cos).ceil() as u32 + 2;
|
||||
|
||||
// Place original in center of expanded transparent canvas
|
||||
let mut expanded = image::RgbaImage::new(new_w, new_h);
|
||||
let ox = (new_w.saturating_sub(img.width())) / 2;
|
||||
let oy = (new_h.saturating_sub(img.height())) / 2;
|
||||
image::imageops::overlay(&mut expanded, img, ox as i64, oy as i64);
|
||||
|
||||
imageproc::geometric_transformations::rotate_about_center(
|
||||
&expanded,
|
||||
radians,
|
||||
imageproc::geometric_transformations::Interpolation::Bilinear,
|
||||
Rgba([0, 0, 0, 0]),
|
||||
).into()
|
||||
}
|
||||
|
||||
/// Rotate an RGBA image by the given WatermarkRotation
|
||||
fn rotate_watermark_image(
|
||||
img: DynamicImage,
|
||||
@@ -175,20 +208,13 @@ fn rotate_watermark_image(
|
||||
match rotation {
|
||||
super::WatermarkRotation::Degrees90 => img.rotate90(),
|
||||
super::WatermarkRotation::Degrees45 => {
|
||||
imageproc::geometric_transformations::rotate_about_center(
|
||||
&img.to_rgba8(),
|
||||
std::f32::consts::FRAC_PI_4,
|
||||
imageproc::geometric_transformations::Interpolation::Bilinear,
|
||||
Rgba([0, 0, 0, 0]),
|
||||
).into()
|
||||
rotate_on_expanded_canvas(&img.to_rgba8(), std::f32::consts::FRAC_PI_4)
|
||||
}
|
||||
super::WatermarkRotation::DegreesNeg45 => {
|
||||
imageproc::geometric_transformations::rotate_about_center(
|
||||
&img.to_rgba8(),
|
||||
-std::f32::consts::FRAC_PI_4,
|
||||
imageproc::geometric_transformations::Interpolation::Bilinear,
|
||||
Rgba([0, 0, 0, 0]),
|
||||
).into()
|
||||
rotate_on_expanded_canvas(&img.to_rgba8(), -std::f32::consts::FRAC_PI_4)
|
||||
}
|
||||
super::WatermarkRotation::Custom(degrees) => {
|
||||
rotate_on_expanded_canvas(&img.to_rgba8(), degrees.to_radians())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +230,9 @@ fn apply_text_watermark(
|
||||
rotation: Option<super::WatermarkRotation>,
|
||||
margin_px: u32,
|
||||
) -> Result<DynamicImage> {
|
||||
if text.is_empty() {
|
||||
return Ok(img);
|
||||
}
|
||||
let font_data = find_system_font(font_family)?;
|
||||
let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
|
||||
PixstripError::Processing {
|
||||
@@ -234,8 +263,8 @@ fn apply_text_watermark(
|
||||
} else {
|
||||
// No rotation - draw text directly (faster)
|
||||
let scale = ab_glyph::PxScale::from(font_size);
|
||||
let text_width = (text.len() as f32 * font_size * 0.6) as u32;
|
||||
let text_height = font_size as u32;
|
||||
let text_width = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as u32).saturating_add(4).min(8192);
|
||||
let text_height = ((font_size.min(1000.0) * 1.4) as u32).saturating_add(4).min(4096);
|
||||
let text_dims = Dimensions {
|
||||
width: text_width,
|
||||
height: text_height,
|
||||
@@ -266,6 +295,9 @@ fn apply_tiled_text_watermark(
|
||||
rotation: Option<super::WatermarkRotation>,
|
||||
margin: u32,
|
||||
) -> Result<DynamicImage> {
|
||||
if text.is_empty() {
|
||||
return Ok(img);
|
||||
}
|
||||
let font_data = find_system_font(font_family)?;
|
||||
let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
|
||||
PixstripError::Processing {
|
||||
@@ -301,17 +333,17 @@ fn apply_tiled_text_watermark(
|
||||
let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
|
||||
let draw_color = Rgba([color[0], color[1], color[2], alpha]);
|
||||
|
||||
let text_width = (text.len() as f32 * font_size * 0.6) as u32;
|
||||
let text_height = font_size as u32;
|
||||
let text_width = ((text.len().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as i64 + 4).min(8192);
|
||||
let text_height = ((font_size.min(1000.0) * 1.4) as i64 + 4).min(4096);
|
||||
|
||||
let mut y = spacing as i32;
|
||||
while y < ih as i32 {
|
||||
let mut x = spacing as i32;
|
||||
while x < iw as i32 {
|
||||
draw_text_mut(&mut rgba, draw_color, x, y, scale, &font, text);
|
||||
x += text_width as i32 + spacing as i32;
|
||||
let mut y = spacing as i64;
|
||||
while y < ih as i64 {
|
||||
let mut x = spacing as i64;
|
||||
while x < iw as i64 {
|
||||
draw_text_mut(&mut rgba, draw_color, x as i32, y as i32, scale, &font, text);
|
||||
x += text_width + spacing as i64;
|
||||
}
|
||||
y += text_height as i32 + spacing as i32;
|
||||
y += text_height + spacing as i64;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +422,7 @@ fn apply_image_watermark(
|
||||
opacity: f32,
|
||||
scale: f32,
|
||||
rotation: Option<super::WatermarkRotation>,
|
||||
margin: u32,
|
||||
) -> Result<DynamicImage> {
|
||||
let watermark = image::open(watermark_path).map_err(|e| PixstripError::Processing {
|
||||
operation: "watermark".into(),
|
||||
@@ -423,7 +456,6 @@ fn apply_image_watermark(
|
||||
height: watermark.height(),
|
||||
};
|
||||
|
||||
let margin = 10;
|
||||
let (x, y) = calculate_position(position, image_dims, wm_dims, margin);
|
||||
|
||||
let mut base = img.into_rgba8();
|
||||
|
||||
Reference in New Issue
Block a user