Wire all missing operations into pipeline executor
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
use image::{DynamicImage, Rgba};
|
||||
use imageproc::drawing::draw_text_mut;
|
||||
|
||||
use crate::error::{PixstripError, Result};
|
||||
use crate::types::Dimensions;
|
||||
use super::WatermarkPosition;
|
||||
|
||||
use super::{WatermarkConfig, WatermarkPosition};
|
||||
|
||||
pub fn calculate_position(
|
||||
position: WatermarkPosition,
|
||||
@@ -12,10 +17,10 @@ pub fn calculate_position(
|
||||
let ww = watermark_size.width;
|
||||
let wh = watermark_size.height;
|
||||
|
||||
let center_x = (iw - ww) / 2;
|
||||
let center_y = (ih - wh) / 2;
|
||||
let right_x = iw - ww - margin;
|
||||
let bottom_y = ih - wh - margin;
|
||||
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);
|
||||
|
||||
match position {
|
||||
WatermarkPosition::TopLeft => (margin, margin),
|
||||
@@ -29,3 +34,167 @@ pub fn calculate_position(
|
||||
WatermarkPosition::BottomRight => (right_x, bottom_y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_watermark(
|
||||
img: DynamicImage,
|
||||
config: &WatermarkConfig,
|
||||
) -> Result<DynamicImage> {
|
||||
match config {
|
||||
WatermarkConfig::Text {
|
||||
text,
|
||||
position,
|
||||
font_size,
|
||||
opacity,
|
||||
color,
|
||||
} => apply_text_watermark(img, text, *position, *font_size, *opacity, *color),
|
||||
WatermarkConfig::Image {
|
||||
path,
|
||||
position,
|
||||
opacity,
|
||||
scale,
|
||||
} => apply_image_watermark(img, path, *position, *opacity, *scale),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_system_font() -> Result<Vec<u8>> {
|
||||
let candidates = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
||||
"/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
|
||||
"/usr/share/fonts/noto/NotoSans-Regular.ttf",
|
||||
];
|
||||
|
||||
for path in &candidates {
|
||||
if let Ok(data) = std::fs::read(path) {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
|
||||
Err(PixstripError::Processing {
|
||||
operation: "watermark".into(),
|
||||
reason: "No system font found for text watermark".into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_text_watermark(
|
||||
img: DynamicImage,
|
||||
text: &str,
|
||||
position: WatermarkPosition,
|
||||
font_size: f32,
|
||||
opacity: f32,
|
||||
color: [u8; 4],
|
||||
) -> Result<DynamicImage> {
|
||||
let font_data = find_system_font()?;
|
||||
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);
|
||||
|
||||
// Estimate text dimensions for positioning
|
||||
let text_width = (text.len() as f32 * font_size * 0.6) as u32;
|
||||
let text_height = font_size as u32;
|
||||
let text_dims = Dimensions {
|
||||
width: text_width,
|
||||
height: text_height,
|
||||
};
|
||||
|
||||
let image_dims = Dimensions {
|
||||
width: img.width(),
|
||||
height: img.height(),
|
||||
};
|
||||
|
||||
let margin = (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;
|
||||
let draw_color = Rgba([color[0], color[1], color[2], alpha]);
|
||||
|
||||
let mut rgba = img.into_rgba8();
|
||||
draw_text_mut(&mut rgba, draw_color, x as i32, y as i32, scale, &font, text);
|
||||
|
||||
Ok(DynamicImage::ImageRgba8(rgba))
|
||||
}
|
||||
|
||||
fn apply_image_watermark(
|
||||
img: DynamicImage,
|
||||
watermark_path: &std::path::Path,
|
||||
position: WatermarkPosition,
|
||||
opacity: f32,
|
||||
scale: f32,
|
||||
) -> Result<DynamicImage> {
|
||||
let watermark = image::open(watermark_path).map_err(|e| PixstripError::Processing {
|
||||
operation: "watermark".into(),
|
||||
reason: format!("Failed to load watermark image: {}", e),
|
||||
})?;
|
||||
|
||||
// Scale the watermark
|
||||
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 image_dims = Dimensions {
|
||||
width: img.width(),
|
||||
height: img.height(),
|
||||
};
|
||||
let wm_dims = Dimensions {
|
||||
width: wm_width,
|
||||
height: wm_height,
|
||||
};
|
||||
|
||||
let margin = 10;
|
||||
let (x, y) = calculate_position(position, image_dims, wm_dims, margin);
|
||||
|
||||
let mut base = img.into_rgba8();
|
||||
let overlay = watermark.to_rgba8();
|
||||
|
||||
let alpha_factor = opacity.clamp(0.0, 1.0);
|
||||
|
||||
for oy in 0..wm_height {
|
||||
for ox in 0..wm_width {
|
||||
let px = x + ox;
|
||||
let py = y + oy;
|
||||
|
||||
if px < base.width() && py < base.height() {
|
||||
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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DynamicImage::ImageRgba8(base))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user