Add watermark tiling, rotation types, margin/scale controls

This commit is contained in:
2026-03-06 17:36:07 +02:00
parent 3e6a539749
commit 646a698ac1
5 changed files with 181 additions and 5 deletions

View File

@@ -47,13 +47,31 @@ pub fn apply_watermark(
opacity,
color,
font_family,
} => apply_text_watermark(img, text, *position, *font_size, *opacity, *color, font_family.as_deref()),
rotation,
tiled,
margin,
} => {
if *tiled {
apply_tiled_text_watermark(img, text, *font_size, *opacity, *color, font_family.as_deref(), *rotation, *margin)
} else {
apply_text_watermark(img, text, *position, *font_size, *opacity, *color, font_family.as_deref(), *rotation, *margin)
}
}
WatermarkConfig::Image {
path,
position,
opacity,
scale,
} => apply_image_watermark(img, path, *position, *opacity, *scale),
rotation: _,
tiled,
margin,
} => {
if *tiled {
apply_tiled_image_watermark(img, path, *opacity, *scale, *margin)
} else {
apply_image_watermark(img, path, *position, *opacity, *scale)
}
}
}
}
@@ -137,6 +155,8 @@ fn apply_text_watermark(
opacity: f32,
color: [u8; 4],
font_family: Option<&str>,
_rotation: Option<super::WatermarkRotation>,
margin_px: u32,
) -> Result<DynamicImage> {
let font_data = find_system_font(font_family)?;
let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
@@ -161,7 +181,7 @@ fn apply_text_watermark(
height: img.height(),
};
let margin = (font_size * 0.5) as u32;
let margin = if margin_px > 0 { margin_px } else { (font_size * 0.5) as u32 };
let (x, y) = calculate_position(position, image_dims, text_dims, margin);
let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
@@ -173,6 +193,107 @@ fn apply_text_watermark(
Ok(DynamicImage::ImageRgba8(rgba))
}
fn apply_tiled_text_watermark(
img: DynamicImage,
text: &str,
font_size: f32,
opacity: f32,
color: [u8; 4],
font_family: Option<&str>,
_rotation: Option<super::WatermarkRotation>,
margin: u32,
) -> Result<DynamicImage> {
let font_data = find_system_font(font_family)?;
let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
PixstripError::Processing {
operation: "watermark".into(),
reason: "Failed to load font".into(),
}
})?;
let scale = ab_glyph::PxScale::from(font_size);
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 spacing = margin.max(20);
let mut rgba = img.into_rgba8();
let (iw, ih) = (rgba.width(), rgba.height());
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;
}
y += text_height as i32 + spacing as i32;
}
Ok(DynamicImage::ImageRgba8(rgba))
}
fn apply_tiled_image_watermark(
img: DynamicImage,
watermark_path: &std::path::Path,
opacity: f32,
scale: f32,
margin: u32,
) -> Result<DynamicImage> {
let watermark = image::open(watermark_path).map_err(|e| PixstripError::Processing {
operation: "watermark".into(),
reason: format!("Failed to load watermark image: {}", e),
})?;
let wm_width = (watermark.width() as f32 * scale) as u32;
let wm_height = (watermark.height() as f32 * scale) as u32;
if wm_width == 0 || wm_height == 0 {
return Ok(img);
}
let watermark = watermark.resize_exact(wm_width, wm_height, image::imageops::FilterType::Lanczos3);
let overlay = watermark.to_rgba8();
let alpha_factor = opacity.clamp(0.0, 1.0);
let spacing = margin.max(10);
let mut base = img.into_rgba8();
let (iw, ih) = (base.width(), base.height());
let mut ty = spacing;
while ty < ih {
let mut tx = spacing;
while tx < iw {
for oy in 0..wm_height {
for ox in 0..wm_width {
let px = tx + ox;
let py = ty + oy;
if px < iw && py < ih {
let wm_pixel = overlay.get_pixel(ox, oy);
let base_pixel = base.get_pixel(px, py);
let wm_alpha = (wm_pixel[3] as f32 / 255.0) * alpha_factor;
let base_alpha = base_pixel[3] as f32 / 255.0;
let out_alpha = wm_alpha + base_alpha * (1.0 - wm_alpha);
if out_alpha > 0.0 {
let r = ((wm_pixel[0] as f32 * wm_alpha + base_pixel[0] as f32 * base_alpha * (1.0 - wm_alpha)) / out_alpha) as u8;
let g = ((wm_pixel[1] as f32 * wm_alpha + base_pixel[1] as f32 * base_alpha * (1.0 - wm_alpha)) / out_alpha) as u8;
let b = ((wm_pixel[2] as f32 * wm_alpha + base_pixel[2] as f32 * base_alpha * (1.0 - wm_alpha)) / out_alpha) as u8;
let a = (out_alpha * 255.0) as u8;
base.put_pixel(px, py, Rgba([r, g, b, a]));
}
}
}
}
tx += wm_width + spacing;
}
ty += wm_height + spacing;
}
Ok(DynamicImage::ImageRgba8(base))
}
fn apply_image_watermark(
img: DynamicImage,
watermark_path: &std::path::Path,