image_renderer/
surface.rs

1//! 图片表面(Surface)模块
2//!
3//! 封装 image 库,提供图片的加载、保存和基本操作
4
5use crate::error::{ImageError, ImageResult};
6use image::{ImageBuffer, Rgba, RgbaImage};
7use std::path::Path;
8
9/// 图片表面,用于存储和操作图片数据
10#[derive(Clone)]
11pub struct Surface {
12    /// 内部图片数据
13    image: RgbaImage,
14}
15
16impl Surface {
17    /// 创建新的空白表面
18    pub fn new(width: u32, height: u32) -> ImageResult<Self> {
19        if width == 0 || height == 0 {
20            return Err(ImageError::InvalidDimensions { width, height });
21        }
22
23        let image = ImageBuffer::new(width, height);
24        Ok(Self { image })
25    }
26
27    /// 创建指定颜色的表面
28    pub fn new_with_color(width: u32, height: u32, color: Rgba<u8>) -> ImageResult<Self> {
29        if width == 0 || height == 0 {
30            return Err(ImageError::InvalidDimensions { width, height });
31        }
32
33        let image = ImageBuffer::from_pixel(width, height, color);
34        Ok(Self { image })
35    }
36
37    /// 从文件加载图片
38    pub fn from_file<P: AsRef<Path>>(path: P) -> ImageResult<Self> {
39        let dynamic_image = image::open(path.as_ref())
40            .map_err(|e| ImageError::LoadError(format!("Failed to open image: {}", e)))?;
41
42        Ok(Self {
43            image: dynamic_image.to_rgba8(),
44        })
45    }
46
47    /// 从字节数据加载图片
48    pub fn from_bytes(data: &[u8]) -> ImageResult<Self> {
49        let dynamic_image = image::load_from_memory(data).map_err(|e| {
50            ImageError::LoadError(format!("Failed to load image from memory: {}", e))
51        })?;
52
53        Ok(Self {
54            image: dynamic_image.to_rgba8(),
55        })
56    }
57
58    /// 从 RgbaImage 创建
59    pub fn from_rgba_image(image: RgbaImage) -> Self {
60        Self { image }
61    }
62
63    /// 保存图片到文件
64    pub fn save<P: AsRef<Path>>(&self, path: P) -> ImageResult<()> {
65        self.image
66            .save(path.as_ref())
67            .map_err(|e| ImageError::SaveError(format!("Failed to save image: {}", e)))
68    }
69
70    /// 保存为 PNG 格式的字节数据
71    pub fn to_png_bytes(&self) -> ImageResult<Vec<u8>> {
72        let mut bytes = Vec::new();
73        let mut cursor = std::io::Cursor::new(&mut bytes);
74
75        self.image
76            .write_to(&mut cursor, image::ImageFormat::Png)
77            .map_err(|e| ImageError::SaveError(format!("Failed to encode PNG: {}", e)))?;
78
79        Ok(bytes)
80    }
81
82    /// 保存为 JPEG 格式的字节数据
83    pub fn to_jpeg_bytes(&self, quality: u8) -> ImageResult<Vec<u8>> {
84        let mut bytes = Vec::new();
85        let mut cursor = std::io::Cursor::new(&mut bytes);
86
87        let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut cursor, quality);
88        self.image
89            .write_with_encoder(encoder)
90            .map_err(|e| ImageError::SaveError(format!("Failed to encode JPEG: {}", e)))?;
91
92        Ok(bytes)
93    }
94
95    /// 获取图片宽度
96    pub fn width(&self) -> u32 {
97        self.image.width()
98    }
99
100    /// 获取图片高度
101    pub fn height(&self) -> u32 {
102        self.image.height()
103    }
104
105    /// 获取图片尺寸
106    pub fn dimensions(&self) -> (u32, u32) {
107        self.image.dimensions()
108    }
109
110    /// 获取可变的图片数据引用
111    pub(crate) fn image_mut(&mut self) -> &mut RgbaImage {
112        &mut self.image
113    }
114
115    /// 获取图片数据引用
116    #[allow(dead_code)]
117    pub(crate) fn image(&self) -> &RgbaImage {
118        &self.image
119    }
120
121    /// 获取指定位置的像素
122    pub fn get_pixel(&self, x: u32, y: u32) -> Option<Rgba<u8>> {
123        if x < self.width() && y < self.height() {
124            Some(*self.image.get_pixel(x, y))
125        } else {
126            None
127        }
128    }
129
130    /// 设置指定位置的像素
131    pub fn put_pixel(&mut self, x: u32, y: u32, color: Rgba<u8>) {
132        if x < self.width() && y < self.height() {
133            self.image.put_pixel(x, y, color);
134        }
135    }
136
137    /// 清空表面为指定颜色
138    pub fn clear(&mut self, color: Rgba<u8>) {
139        for pixel in self.image.pixels_mut() {
140            *pixel = color;
141        }
142    }
143
144    /// 裁剪图片
145    pub fn crop(&self, x: u32, y: u32, width: u32, height: u32) -> ImageResult<Self> {
146        if x + width > self.width() || y + height > self.height() {
147            return Err(ImageError::ProcessingError(
148                "Crop region exceeds image bounds".to_string(),
149            ));
150        }
151
152        let cropped = image::imageops::crop_imm(&self.image, x, y, width, height).to_image();
153        Ok(Self::from_rgba_image(cropped))
154    }
155
156    /// 调整图片大小
157    pub fn resize(&self, width: u32, height: u32) -> ImageResult<Self> {
158        if width == 0 || height == 0 {
159            return Err(ImageError::InvalidDimensions { width, height });
160        }
161
162        let resized = image::imageops::resize(
163            &self.image,
164            width,
165            height,
166            image::imageops::FilterType::Lanczos3,
167        );
168        Ok(Self::from_rgba_image(resized))
169    }
170
171    /// 水平翻转
172    pub fn flip_horizontal(&self) -> Self {
173        let flipped = image::imageops::flip_horizontal(&self.image);
174        Self::from_rgba_image(flipped)
175    }
176
177    /// 垂直翻转
178    pub fn flip_vertical(&self) -> Self {
179        let flipped = image::imageops::flip_vertical(&self.image);
180        Self::from_rgba_image(flipped)
181    }
182
183    /// 旋转 90 度
184    pub fn rotate90(&self) -> Self {
185        let rotated = image::imageops::rotate90(&self.image);
186        Self::from_rgba_image(rotated)
187    }
188
189    /// 旋转 180 度
190    pub fn rotate180(&self) -> Self {
191        let rotated = image::imageops::rotate180(&self.image);
192        Self::from_rgba_image(rotated)
193    }
194
195    /// 旋转 270 度
196    pub fn rotate270(&self) -> Self {
197        let rotated = image::imageops::rotate270(&self.image);
198        Self::from_rgba_image(rotated)
199    }
200
201    /// 克隆表面
202    pub fn clone_surface(&self) -> Self {
203        Self {
204            image: self.image.clone(),
205        }
206    }
207}