use super::FocusStackError;
use crate::types::CameraFrame;
#[derive(Debug, Clone)]
pub struct AlignmentResult {
pub translation: (f32, f32),
pub rotation: f32,
pub scale: f32,
pub error: f32,
}
impl Default for AlignmentResult {
fn default() -> Self {
Self {
translation: (0.0, 0.0),
rotation: 0.0,
scale: 1.0,
error: 0.0,
}
}
}
pub fn align_frames(frames: &[CameraFrame]) -> Result<Vec<AlignmentResult>, FocusStackError> {
if frames.len() < 2 {
return Err(FocusStackError::InsufficientImages {
required: 2,
provided: frames.len(),
});
}
log::info!("Aligning {} frames", frames.len());
let reference = &frames[0];
let mut results = Vec::with_capacity(frames.len());
results.push(AlignmentResult::default());
for (idx, frame) in frames.iter().enumerate().skip(1) {
log::debug!("Aligning frame {} to reference", idx);
if frame.width != reference.width || frame.height != reference.height {
return Err(FocusStackError::DimensionMismatch {
expected: (reference.width, reference.height),
got: (frame.width, frame.height),
});
}
let alignment = compute_alignment_simple(reference, frame)?;
log::debug!(
"Frame {} alignment: translation=({:.2}, {:.2}), error={:.3}",
idx,
alignment.translation.0,
alignment.translation.1,
alignment.error
);
results.push(alignment);
}
log::info!("Alignment complete");
Ok(results)
}
pub fn apply_alignment(
frame: &CameraFrame,
alignment: &AlignmentResult,
) -> Result<CameraFrame, FocusStackError> {
if alignment.translation == (0.0, 0.0) && alignment.rotation == 0.0 && alignment.scale == 1.0 {
return Ok(frame.clone());
}
log::debug!(
"Applying alignment: translation=({:.2}, {:.2}), rotation={:.4}, scale={:.4}",
alignment.translation.0,
alignment.translation.1,
alignment.rotation,
alignment.scale
);
let mut aligned = frame.clone();
let tx = alignment.translation.0.round() as i32;
let ty = alignment.translation.1.round() as i32;
if tx != 0 || ty != 0 {
apply_translation(&mut aligned, tx, ty);
}
if alignment.rotation.abs() > 0.01 {
apply_rotation(&mut aligned, alignment.rotation);
}
if (alignment.scale - 1.0).abs() > 0.01 {
apply_scale(&mut aligned, alignment.scale);
}
Ok(aligned)
}
fn compute_alignment_simple(
reference: &CameraFrame,
frame: &CameraFrame,
) -> Result<AlignmentResult, FocusStackError> {
let ref_com = compute_center_of_mass(reference);
let frame_com = compute_center_of_mass(frame);
let translation = (frame_com.0 - ref_com.0, frame_com.1 - ref_com.1);
let error = (translation.0.powi(2) + translation.1.powi(2)).sqrt();
Ok(AlignmentResult {
translation,
rotation: 0.0,
scale: 1.0,
error,
})
}
fn compute_center_of_mass(frame: &CameraFrame) -> (f32, f32) {
let width = frame.width as usize;
let height = frame.height as usize;
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_weight = 0.0;
for y in (0..height).step_by(4) {
for x in (0..width).step_by(4) {
let idx = (y * width + x) * 3;
if idx + 2 < frame.data.len() {
let r = frame.data[idx] as f32;
let g = frame.data[idx + 1] as f32;
let b = frame.data[idx + 2] as f32;
let weight = 0.299 * r + 0.587 * g + 0.114 * b;
sum_x += x as f32 * weight;
sum_y += y as f32 * weight;
sum_weight += weight;
}
}
}
if sum_weight > 0.0 {
(sum_x / sum_weight, sum_y / sum_weight)
} else {
(width as f32 / 2.0, height as f32 / 2.0)
}
}
fn apply_translation(frame: &mut CameraFrame, tx: i32, ty: i32) {
if tx == 0 && ty == 0 {
return;
}
let width = frame.width as i32;
let height = frame.height as i32;
let mut new_data = vec![0u8; frame.data.len()];
for y in 0..height {
for x in 0..width {
let src_x = x - tx;
let src_y = y - ty;
if src_x >= 0 && src_x < width && src_y >= 0 && src_y < height {
let src_idx = ((src_y * width + src_x) * 3) as usize;
let dst_idx = ((y * width + x) * 3) as usize;
if src_idx + 2 < frame.data.len() && dst_idx + 2 < new_data.len() {
new_data[dst_idx..dst_idx + 3]
.copy_from_slice(&frame.data[src_idx..src_idx + 3]);
}
}
}
}
frame.data = new_data;
}
fn apply_rotation(frame: &mut CameraFrame, rotation: f32) {
if rotation == 0.0 {
return;
}
let width = frame.width as i32;
let height = frame.height as i32;
let cx = width as f32 / 2.0;
let cy = height as f32 / 2.0;
let cos_theta = rotation.cos();
let sin_theta = rotation.sin();
let mut new_data = vec![0u8; frame.data.len()];
for y in 0..height {
for x in 0..width {
let x_centered = x as f32 - cx;
let y_centered = y as f32 - cy;
let src_x = (x_centered * cos_theta - y_centered * sin_theta + cx).round() as i32;
let src_y = (x_centered * sin_theta + y_centered * cos_theta + cy).round() as i32;
if src_x >= 0 && src_x < width && src_y >= 0 && src_y < height {
let src_idx = ((src_y * width + src_x) * 3) as usize;
let dst_idx = ((y * width + x) * 3) as usize;
if src_idx + 2 < frame.data.len() && dst_idx + 2 < new_data.len() {
new_data[dst_idx..dst_idx + 3]
.copy_from_slice(&frame.data[src_idx..src_idx + 3]);
}
}
}
}
frame.data = new_data;
}
fn apply_scale(frame: &mut CameraFrame, scale: f32) {
if scale == 1.0 {
return;
}
let width = frame.width as i32;
let height = frame.height as i32;
let inv_scale = 1.0 / scale;
let mut new_data = vec![0u8; frame.data.len()];
for y in 0..height {
for x in 0..width {
let src_x = (x as f32 * inv_scale).round() as i32;
let src_y = (y as f32 * inv_scale).round() as i32;
if src_x >= 0 && src_x < width && src_y >= 0 && src_y < height {
let src_idx = ((src_y * width + src_x) * 3) as usize;
let dst_idx = ((y * width + x) * 3) as usize;
if src_idx + 2 < frame.data.len() && dst_idx + 2 < new_data.len() {
new_data[dst_idx..dst_idx + 3]
.copy_from_slice(&frame.data[src_idx..src_idx + 3]);
}
}
}
}
frame.data = new_data;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alignment_result_default() {
let result = AlignmentResult::default();
assert_eq!(result.translation, (0.0, 0.0));
assert_eq!(result.rotation, 0.0);
assert_eq!(result.scale, 1.0);
assert_eq!(result.error, 0.0);
}
#[test]
fn test_center_of_mass_uniform() {
let width = 100;
let height = 100;
let data = vec![128u8; width * height * 3];
let frame = CameraFrame::new(data, width as u32, height as u32, "test_device".to_string());
let com = compute_center_of_mass(&frame);
assert!((com.0 - 50.0).abs() < 5.0);
assert!((com.1 - 50.0).abs() < 5.0);
}
#[test]
fn test_translation_application() {
let width = 10;
let height = 10;
let data = vec![128u8; width * height * 3];
let mut frame =
CameraFrame::new(data, width as u32, height as u32, "test_device".to_string());
apply_translation(&mut frame, 2, 2);
assert_eq!(frame.data.len(), width * height * 3);
}
#[test]
fn test_insufficient_frames() {
let frames = vec![];
let result = align_frames(&frames);
assert!(matches!(
result,
Err(FocusStackError::InsufficientImages { .. })
));
}
}