imageslapper 0.2.0

A tool for processing and manipulating images with parallelism and advanced rendering.
Documentation
use image::DynamicImage;

use crate::{geometry::rectangle::Rectangle, rendering::draw::ImageWrapper, text::Text};

pub mod batch;

pub struct ImageBuilder {
    content: ImageWrapper,
}

impl Default for ImageBuilder {
    fn default() -> Self {
        ImageBuilder::new(1000, 1000)
    }
}

impl ImageBuilder {
    pub fn new(width: u32, height: u32) -> Self {
        let content = ImageWrapper::new(DynamicImage::new_rgba8(width, height));
        ImageBuilder { content }
    }

    pub fn from_image(image: DynamicImage) -> Self {
        let content = ImageWrapper::new(image);
        ImageBuilder { content }
    }

    pub fn add_text(&mut self, text: Text) -> &mut Self {
        self.content.draw(&text);
        self
    }

    pub fn add_rectangle(&mut self, rectangle: Rectangle) -> &mut Self {
        self.content.draw(&rectangle);
        self
    }

    /// Optimized version using direct buffer operations and SIMD-friendly operations
    pub fn insert_image(&mut self, image: &DynamicImage, x: u32, y: u32) -> &mut Self {
        let mut base_image = self.content.get_image().to_rgba8();
        let overlay_image = image.to_rgba8();
        
        // Get dimensions
        let (base_width, base_height) = base_image.dimensions();
        let (overlay_width, overlay_height) = overlay_image.dimensions();
        
        // Early bounds checking
        if x >= base_width || y >= base_height {
            return self;
        }
        
        // Calculate actual copy dimensions
        let copy_width = std::cmp::min(overlay_width, base_width - x);
        let copy_height = std::cmp::min(overlay_height, base_height - y);
        
        if copy_width == 0 || copy_height == 0 {
            return self;
        }

        // Get raw buffer access for better performance
        let base_buffer = base_image.as_mut();
        let overlay_buffer = overlay_image.as_raw();
        
        // Process row by row for better cache locality
        for row in 0..copy_height {
            let base_y = y + row;
            let overlay_y = row;
            
            let base_row_start = (base_y * base_width + x) as usize * 4;
            let overlay_row_start = (overlay_y * overlay_width) as usize * 4;
            
            // Process pixels in chunks for better performance
            for col in 0..copy_width {
                let base_idx = base_row_start + (col as usize * 4);
                let overlay_idx = overlay_row_start + (col as usize * 4);
                
                let overlay_alpha = overlay_buffer[overlay_idx + 3];
                
                // Skip transparent pixels (common optimization)
                if overlay_alpha == 0 {
                    continue;
                }
                
                // Direct copy for fully opaque pixels (another common case)
                if overlay_alpha == 255 {
                    base_buffer[base_idx..base_idx + 4]
                        .copy_from_slice(&overlay_buffer[overlay_idx..overlay_idx + 4]);
                    continue;
                }
                
                // Alpha blending for semi-transparent pixels
                let alpha = overlay_alpha as u32;
                let inv_alpha = 255 - alpha;
                
                // Use integer arithmetic instead of floating point
                base_buffer[base_idx] = ((overlay_buffer[overlay_idx] as u32 * alpha + 
                                        base_buffer[base_idx] as u32 * inv_alpha) / 255) as u8;
                base_buffer[base_idx + 1] = ((overlay_buffer[overlay_idx + 1] as u32 * alpha + 
                                            base_buffer[base_idx + 1] as u32 * inv_alpha) / 255) as u8;
                base_buffer[base_idx + 2] = ((overlay_buffer[overlay_idx + 2] as u32 * alpha + 
                                            base_buffer[base_idx + 2] as u32 * inv_alpha) / 255) as u8;
                base_buffer[base_idx + 3] = std::cmp::max(overlay_alpha, base_buffer[base_idx + 3]);
            }
        }
        
        // Update the content with the modified image
        self.content = ImageWrapper::new(DynamicImage::ImageRgba8(base_image));
        self
    }

    /// Alternative version for when you don't need alpha blending (even faster)
    pub fn insert_image_opaque(&mut self, image: &DynamicImage, x: u32, y: u32) -> &mut Self {
        let mut base_image = self.content.get_image().to_rgba8();
        let overlay_image = image.to_rgba8();
        
        let (base_width, base_height) = base_image.dimensions();
        let (overlay_width, overlay_height) = overlay_image.dimensions();
        
        if x >= base_width || y >= base_height {
            return self;
        }
        
        let copy_width = std::cmp::min(overlay_width, base_width - x);
        let copy_height = std::cmp::min(overlay_height, base_height - y);
        
        if copy_width == 0 || copy_height == 0 {
            return self;
        }

        // For opaque copying, we can copy entire rows at once
        let base_buffer = base_image.as_mut();
        let overlay_buffer = overlay_image.as_raw();
        
        for row in 0..copy_height {
            let base_y = y + row;
            let overlay_y = row;
            
            let base_start = (base_y * base_width + x) as usize * 4;
            let base_end = base_start + (copy_width as usize * 4);
            
            let overlay_start = (overlay_y * overlay_width) as usize * 4;
            let overlay_end = overlay_start + (copy_width as usize * 4);
            
            // Copy entire row at once - much faster than pixel by pixel
            base_buffer[base_start..base_end]
                .copy_from_slice(&overlay_buffer[overlay_start..overlay_end]);
        }
        
        self.content = ImageWrapper::new(DynamicImage::ImageRgba8(base_image));
        self
    }

    /// Batch insert multiple images (can be optimized further if needed)
    pub fn insert_images(&mut self, images: &[(DynamicImage, u32, u32)]) -> &mut Self {
        for (image, x, y) in images {
            self.insert_image(image, *x, *y);
        }
        self
    }

    pub fn get_image(&self) -> DynamicImage {
        self.content.get_image().clone()
    }

    pub fn save_image(&self, path: &str) {
        self.content.save_image(path).unwrap();
    }
}

// If you want even more performance, consider this unsafe version
// (use only if you're sure about bounds checking)
impl ImageBuilder {
    /// Unsafe but fastest version - skips bounds checking
    /// ONLY use this if you're 100% sure your coordinates are valid
    ///
    /// # Safety
    /// The caller must ensure that `x` and `y` are valid coordinates such that the overlay image fits entirely
    /// within the base image starting at `(x, y)`. No bounds checking is performed, so passing invalid coordinates
    /// may result in undefined behavior or memory corruption.
    pub unsafe fn insert_image_unchecked(&mut self, image: &DynamicImage, x: u32, y: u32) -> &mut Self {
        let mut base_image = self.content.get_image().to_rgba8();
        let overlay_image = image.to_rgba8();
        
        let (base_width, _) = base_image.dimensions();
        let (overlay_width, overlay_height) = overlay_image.dimensions();
        
        let base_buffer = base_image.as_mut();
        let overlay_buffer = overlay_image.as_raw();
        
        for row in 0..overlay_height {
            let base_row_start = ((y + row) * base_width + x) as usize * 4;
            let overlay_row_start = (row * overlay_width) as usize * 4;
            let row_bytes = (overlay_width as usize) * 4;
            
            // Direct memory copy - fastest possible
            unsafe { std::ptr::copy_nonoverlapping(
                overlay_buffer.as_ptr().add(overlay_row_start),
                base_buffer.as_mut_ptr().add(base_row_start),
                row_bytes
            ) };
        }
        
        self.content = ImageWrapper::new(DynamicImage::ImageRgba8(base_image));
        self
    }
}