rari 0.13.6

rari: Runtime Accelerated Rendering Infrastructure - A high-performance React Server Components framework powered by Rust
Documentation
use super::super::layout::ComputedLayout;
use super::border::BorderRadius;
use super::renderer::ImageRenderer;
use image::RgbaImage;

impl ImageRenderer {
    pub(super) fn render_image(
        &mut self,
        layout: &ComputedLayout,
        canvas: &mut RgbaImage,
    ) -> Result<(), String> {
        let src = layout
            .element
            .props
            .get("src")
            .and_then(|v| v.as_str())
            .ok_or("Image element missing src attribute")?;

        let source_image = self.load_image(src)?;

        let object_fit = layout.style.get("objectFit").map(|s| s.as_str()).unwrap_or("fill");

        let border_radius = self.parse_border_radius(&layout.style);

        let target_width = layout.width as u32;
        let target_height = layout.height as u32;

        let (processed_image, offset_x, offset_y) =
            self.process_object_fit(source_image, target_width, target_height, object_fit)?;

        let x_start = layout.x as u32 + offset_x;
        let y_start = layout.y as u32 + offset_y;

        if border_radius.top_left > 0.0
            || border_radius.top_right > 0.0
            || border_radius.bottom_right > 0.0
            || border_radius.bottom_left > 0.0
        {
            self.render_image_with_border_radius(
                &processed_image,
                canvas,
                x_start,
                y_start,
                border_radius,
            );
        } else {
            for (x, y, pixel) in processed_image.enumerate_pixels() {
                let canvas_x = x_start + x;
                let canvas_y = y_start + y;

                if canvas_x < self.width && canvas_y < self.height {
                    let bg = canvas.get_pixel(canvas_x, canvas_y);
                    let blended = self.alpha_blend(*bg, *pixel);
                    canvas.put_pixel(canvas_x, canvas_y, blended);
                }
            }
        }

        Ok(())
    }

    fn process_object_fit(
        &self,
        source_image: RgbaImage,
        target_width: u32,
        target_height: u32,
        object_fit: &str,
    ) -> Result<(RgbaImage, u32, u32), String> {
        let src_width = source_image.width() as f32;
        let src_height = source_image.height() as f32;
        let target_w = target_width as f32;
        let target_h = target_height as f32;

        match object_fit {
            "contain" => {
                let scale = (target_w / src_width).min(target_h / src_height);
                let new_width = (src_width * scale) as u32;
                let new_height = (src_height * scale) as u32;

                let resized = image::imageops::resize(
                    &source_image,
                    new_width,
                    new_height,
                    image::imageops::FilterType::CatmullRom,
                );

                let offset_x = (target_width - new_width) / 2;
                let offset_y = (target_height - new_height) / 2;

                Ok((resized, offset_x, offset_y))
            }
            "cover" => {
                let scale = (target_w / src_width).max(target_h / src_height);
                let new_width = (src_width * scale) as u32;
                let new_height = (src_height * scale) as u32;

                let resized = image::imageops::resize(
                    &source_image,
                    new_width,
                    new_height,
                    image::imageops::FilterType::CatmullRom,
                );

                let crop_x = (new_width - target_width) / 2;
                let crop_y = (new_height - target_height) / 2;

                let cropped = image::imageops::crop_imm(
                    &resized,
                    crop_x,
                    crop_y,
                    target_width,
                    target_height,
                )
                .to_image();

                Ok((cropped, 0, 0))
            }
            "scale-down" => {
                let scale = (target_w / src_width).min(target_h / src_height).min(1.0);
                let new_width = (src_width * scale) as u32;
                let new_height = (src_height * scale) as u32;

                let resized = if scale < 1.0 {
                    image::imageops::resize(
                        &source_image,
                        new_width,
                        new_height,
                        image::imageops::FilterType::CatmullRom,
                    )
                } else {
                    source_image
                };

                let offset_x = (target_width - new_width) / 2;
                let offset_y = (target_height - new_height) / 2;

                Ok((resized, offset_x, offset_y))
            }
            "none" => {
                let offset_x =
                    if src_width < target_w { ((target_w - src_width) / 2.0) as u32 } else { 0 };
                let offset_y =
                    if src_height < target_h { ((target_h - src_height) / 2.0) as u32 } else { 0 };

                if src_width > target_w || src_height > target_h {
                    let crop_x = if src_width > target_w {
                        ((src_width - target_w) / 2.0) as u32
                    } else {
                        0
                    };
                    let crop_y = if src_height > target_h {
                        ((src_height - target_h) / 2.0) as u32
                    } else {
                        0
                    };

                    let crop_width = src_width.min(target_w) as u32;
                    let crop_height = src_height.min(target_h) as u32;

                    let cropped = image::imageops::crop_imm(
                        &source_image,
                        crop_x,
                        crop_y,
                        crop_width,
                        crop_height,
                    )
                    .to_image();

                    Ok((cropped, offset_x, offset_y))
                } else {
                    Ok((source_image, offset_x, offset_y))
                }
            }
            _ => {
                let resized = if source_image.width() != target_width
                    || source_image.height() != target_height
                {
                    image::imageops::resize(
                        &source_image,
                        target_width,
                        target_height,
                        image::imageops::FilterType::CatmullRom,
                    )
                } else {
                    source_image
                };

                Ok((resized, 0, 0))
            }
        }
    }

    fn render_image_with_border_radius(
        &self,
        image: &RgbaImage,
        canvas: &mut RgbaImage,
        x_start: u32,
        y_start: u32,
        radius: BorderRadius,
    ) {
        let img_width = image.width() as f32;
        let img_height = image.height() as f32;

        for (x, y, pixel) in image.enumerate_pixels() {
            let canvas_x = x_start + x;
            let canvas_y = y_start + y;

            if canvas_x >= self.width || canvas_y >= self.height {
                continue;
            }

            let fx = x as f32;
            let fy = y as f32;

            let in_corner = if fx < radius.top_left && fy < radius.top_left {
                let dx = radius.top_left - fx;
                let dy = radius.top_left - fy;
                dx * dx + dy * dy <= radius.top_left * radius.top_left
            } else if fx >= img_width - radius.top_right && fy < radius.top_right {
                let dx = fx - (img_width - radius.top_right);
                let dy = radius.top_right - fy;
                dx * dx + dy * dy <= radius.top_right * radius.top_right
            } else if fx < radius.bottom_left && fy >= img_height - radius.bottom_left {
                let dx = radius.bottom_left - fx;
                let dy = fy - (img_height - radius.bottom_left);
                dx * dx + dy * dy <= radius.bottom_left * radius.bottom_left
            } else if fx >= img_width - radius.bottom_right
                && fy >= img_height - radius.bottom_right
            {
                let dx = fx - (img_width - radius.bottom_right);
                let dy = fy - (img_height - radius.bottom_right);
                dx * dx + dy * dy <= radius.bottom_right * radius.bottom_right
            } else {
                true
            };

            if in_corner {
                let bg = canvas.get_pixel(canvas_x, canvas_y);
                let blended = self.alpha_blend(*bg, *pixel);
                canvas.put_pixel(canvas_x, canvas_y, blended);
            }
        }
    }

    fn load_image(&self, src: &str) -> Result<RgbaImage, String> {
        if src.starts_with("http://") || src.starts_with("https://") {
            self.load_remote_image(src)
        } else if src.starts_with("data:") {
            self.load_data_url(src)
        } else {
            Ok(image::open(src)
                .map_err(|e| format!("Failed to load image {}: {}", src, e))?
                .to_rgba8())
        }
    }

    fn load_remote_image(&self, url: &str) -> Result<RgbaImage, String> {
        use std::io::Read;

        let client = reqwest::blocking::Client::builder()
            .timeout(std::time::Duration::from_secs(10))
            .build()
            .map_err(|e| format!("Failed to create HTTP client: {}", e))?;

        let response = client
            .get(url)
            .send()
            .map_err(|e| format!("Failed to fetch image from {}: {}", url, e))?;

        if !response.status().is_success() {
            return Err(format!("Failed to fetch image: HTTP {}", response.status()));
        }

        const MAX_IMAGE_SIZE: usize = 10 * 1024 * 1024;
        let mut buffer = Vec::new();
        response
            .take(MAX_IMAGE_SIZE as u64)
            .read_to_end(&mut buffer)
            .map_err(|e| format!("Failed to read image data: {}", e))?;

        if buffer.len() >= MAX_IMAGE_SIZE {
            return Err("Image too large (max 10MB)".to_string());
        }

        Ok(image::load_from_memory(&buffer)
            .map_err(|e| format!("Failed to decode image: {}", e))?
            .to_rgba8())
    }

    fn load_data_url(&self, data_url: &str) -> Result<RgbaImage, String> {
        let parts: Vec<&str> = data_url.splitn(2, ',').collect();
        if parts.len() != 2 {
            return Err("Invalid data URL format".to_string());
        }

        let header = parts[0];
        let data = parts[1];

        if header.contains("base64") {
            use base64::{Engine as _, engine::general_purpose};
            let decoded = general_purpose::STANDARD
                .decode(data)
                .map_err(|e| format!("Failed to decode base64: {}", e))?;

            Ok(image::load_from_memory(&decoded)
                .map_err(|e| format!("Failed to decode image: {}", e))?
                .to_rgba8())
        } else {
            Err("Only base64 data URLs are supported".to_string())
        }
    }
}