clipboard_rs/
common.rs

1#[cfg(feature = "image")]
2use image::imageops::FilterType;
3#[cfg(feature = "image")]
4use image::{ColorType, DynamicImage, GenericImageView, ImageFormat, RgbaImage};
5use std::error::Error;
6use std::io::Cursor;
7pub type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync + 'static>>;
8
9pub trait ContentData {
10	fn get_format(&self) -> ContentFormat;
11
12	fn as_bytes(&self) -> &[u8];
13
14	fn as_str(&self) -> Result<&str>;
15}
16
17pub trait ClipboardHandler {
18	fn on_clipboard_change(&mut self);
19}
20
21pub enum ClipboardContent {
22	Text(String),
23	Rtf(String),
24	Html(String),
25	#[cfg(feature = "image")]
26	Image(RustImageData),
27	Files(Vec<String>),
28	Other(String, Vec<u8>),
29}
30
31impl ContentData for ClipboardContent {
32	fn get_format(&self) -> ContentFormat {
33		match self {
34			ClipboardContent::Text(_) => ContentFormat::Text,
35			ClipboardContent::Rtf(_) => ContentFormat::Rtf,
36			ClipboardContent::Html(_) => ContentFormat::Html,
37			#[cfg(feature = "image")]
38			ClipboardContent::Image(_) => ContentFormat::Image,
39			ClipboardContent::Files(_) => ContentFormat::Files,
40			ClipboardContent::Other(format, _) => ContentFormat::Other(format.clone()),
41		}
42	}
43
44	fn as_bytes(&self) -> &[u8] {
45		match self {
46			ClipboardContent::Text(data) => data.as_bytes(),
47			ClipboardContent::Rtf(data) => data.as_bytes(),
48			ClipboardContent::Html(data) => data.as_bytes(),
49			// dynamic image is not supported to as bytes
50			#[cfg(feature = "image")]
51			ClipboardContent::Image(_) => &[],
52			ClipboardContent::Files(data) => {
53				// use first file path as data
54				if let Some(path) = data.first() {
55					path.as_bytes()
56				} else {
57					&[]
58				}
59			}
60			ClipboardContent::Other(_, data) => data.as_slice(),
61		}
62	}
63
64	fn as_str(&self) -> Result<&str> {
65		match self {
66			ClipboardContent::Text(data) => Ok(data),
67			ClipboardContent::Rtf(data) => Ok(data),
68			ClipboardContent::Html(data) => Ok(data),
69			#[cfg(feature = "image")]
70			ClipboardContent::Image(_) => Err("can't convert image to string".into()),
71			ClipboardContent::Files(data) => {
72				// use first file path as data
73				if let Some(path) = data.first() {
74					Ok(path)
75				} else {
76					Err("content is empty".into())
77				}
78			}
79			ClipboardContent::Other(_, data) => std::str::from_utf8(data).map_err(|e| e.into()),
80		}
81	}
82}
83
84#[derive(Clone)]
85pub enum ContentFormat {
86	Text,
87	Rtf,
88	Html,
89	#[cfg(feature = "image")]
90	Image,
91	Files,
92	Other(String),
93}
94
95#[cfg(feature = "image")]
96pub struct RustImageData {
97	width: u32,
98	height: u32,
99	data: Option<DynamicImage>,
100}
101
102/// 此处的 `RustImageBuffer` 已经是带有图片格式的字节流,例如 png,jpeg;
103#[cfg(feature = "image")]
104pub struct RustImageBuffer(Vec<u8>);
105
106#[cfg(feature = "image")]
107pub trait RustImage: Sized {
108	/// create an empty image
109	fn empty() -> Self;
110
111	fn is_empty(&self) -> bool;
112
113	/// Read image from file path
114	fn from_path(path: &str) -> Result<Self>;
115
116	/// Create a new image from a byte slice
117	fn from_bytes(bytes: &[u8]) -> Result<Self>;
118
119	fn from_dynamic_image(image: DynamicImage) -> Self;
120
121	/// width and height
122	fn get_size(&self) -> (u32, u32);
123
124	/// Scale this image down to fit within a specific size.
125	/// Returns a new image. The image's aspect ratio is preserved.
126	/// The image is scaled to the maximum possible size that fits
127	/// within the bounds specified by `nwidth` and `nheight`.
128	///
129	/// This method uses a fast integer algorithm where each source
130	/// pixel contributes to exactly one target pixel.
131	/// May give aliasing artifacts if new size is close to old size.
132	fn thumbnail(&self, width: u32, height: u32) -> Result<Self>;
133
134	/// en: Adjust the size of the image without retaining the aspect ratio
135	/// zh: 调整图片大小,不保留长宽比
136	fn resize(&self, width: u32, height: u32, filter: FilterType) -> Result<Self>;
137
138	fn encode_image(
139		&self,
140		target_color_type: ColorType,
141		format: ImageFormat,
142	) -> Result<RustImageBuffer>;
143
144	fn to_jpeg(&self) -> Result<RustImageBuffer>;
145
146	/// en: Convert to png format, the returned image is a new image, and the data itself will not be modified
147	/// zh: 转为 png 格式,返回的为新的图片,本身数据不会修改
148	fn to_png(&self) -> Result<RustImageBuffer>;
149
150	#[cfg(target_os = "windows")]
151	fn to_bitmap(&self) -> Result<RustImageBuffer>;
152
153	fn save_to_path(&self, path: &str) -> Result<()>;
154
155	fn get_dynamic_image(&self) -> Result<DynamicImage>;
156
157	fn to_rgba8(&self) -> Result<RgbaImage>;
158}
159
160#[cfg(feature = "image")]
161impl RustImage for RustImageData {
162	fn empty() -> Self {
163		RustImageData {
164			width: 0,
165			height: 0,
166			data: None,
167		}
168	}
169
170	fn is_empty(&self) -> bool {
171		self.data.is_none()
172	}
173
174	fn from_path(path: &str) -> Result<Self> {
175		let image = image::open(path)?;
176		let (width, height) = image.dimensions();
177		Ok(RustImageData {
178			width,
179			height,
180			data: Some(image),
181		})
182	}
183
184	fn from_bytes(bytes: &[u8]) -> Result<Self> {
185		let image = image::load_from_memory(bytes)?;
186		let (width, height) = image.dimensions();
187		Ok(RustImageData {
188			width,
189			height,
190			data: Some(image),
191		})
192	}
193
194	fn from_dynamic_image(image: DynamicImage) -> Self {
195		let (width, height) = image.dimensions();
196		RustImageData {
197			width,
198			height,
199			data: Some(image),
200		}
201	}
202
203	fn get_size(&self) -> (u32, u32) {
204		(self.width, self.height)
205	}
206
207	fn thumbnail(&self, width: u32, height: u32) -> Result<Self> {
208		match &self.data {
209			Some(image) => {
210				let resized = image.thumbnail(width, height);
211				Ok(RustImageData {
212					width: resized.width(),
213					height: resized.height(),
214					data: Some(resized),
215				})
216			}
217			None => Err("image is empty".into()),
218		}
219	}
220
221	fn resize(&self, width: u32, height: u32, filter: FilterType) -> Result<Self> {
222		match &self.data {
223			Some(image) => {
224				let resized = image.resize_exact(width, height, filter);
225				Ok(RustImageData {
226					width: resized.width(),
227					height: resized.height(),
228					data: Some(resized),
229				})
230			}
231			None => Err("image is empty".into()),
232		}
233	}
234
235	fn save_to_path(&self, path: &str) -> Result<()> {
236		match &self.data {
237			Some(image) => {
238				image.save(path)?;
239				Ok(())
240			}
241			None => Err("image is empty".into()),
242		}
243	}
244
245	fn get_dynamic_image(&self) -> Result<DynamicImage> {
246		match &self.data {
247			Some(image) => Ok(image.clone()),
248			None => Err("image is empty".into()),
249		}
250	}
251
252	fn to_rgba8(&self) -> Result<RgbaImage> {
253		match &self.data {
254			Some(image) => Ok(image.to_rgba8()),
255			None => Err("image is empty".into()),
256		}
257	}
258
259	// 私有辅助函数,处理图像格式转换和编码
260	fn encode_image(
261		&self,
262		target_color_type: ColorType,
263		format: ImageFormat,
264	) -> Result<RustImageBuffer> {
265		let image = self.data.as_ref().ok_or("image is empty")?;
266
267		let mut bytes = Vec::new();
268		match (image.color(), target_color_type) {
269			(ColorType::Rgba8, ColorType::Rgb8) => image
270				.to_rgb8()
271				.write_to(&mut Cursor::new(&mut bytes), format)?,
272			(_, ColorType::Rgba8) => image
273				.to_rgba8()
274				.write_to(&mut Cursor::new(&mut bytes), format)?,
275			_ => image.write_to(&mut Cursor::new(&mut bytes), format)?,
276		};
277		Ok(RustImageBuffer(bytes))
278	}
279
280	fn to_jpeg(&self) -> Result<RustImageBuffer> {
281		// JPEG 需要 RGB 格式(不支持 alpha 通道)
282		self.encode_image(ColorType::Rgb8, ImageFormat::Jpeg)
283	}
284
285	fn to_png(&self) -> Result<RustImageBuffer> {
286		// PNG 使用 RGBA 格式以支持透明度
287		self.encode_image(ColorType::Rgba8, ImageFormat::Png)
288	}
289
290	#[cfg(target_os = "windows")]
291	fn to_bitmap(&self) -> Result<RustImageBuffer> {
292		// BMP 使用 RGBA 格式
293		self.encode_image(ColorType::Rgba8, ImageFormat::Bmp)
294	}
295}
296
297#[cfg(feature = "image")]
298impl RustImageBuffer {
299	pub fn get_bytes(&self) -> &[u8] {
300		&self.0
301	}
302
303	pub fn save_to_path(&self, path: &str) -> Result<()> {
304		std::fs::write(path, &self.0)?;
305		Ok(())
306	}
307}