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 #[cfg(feature = "image")]
51 ClipboardContent::Image(_) => &[],
52 ClipboardContent::Files(data) => {
53 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 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#[cfg(feature = "image")]
104pub struct RustImageBuffer(Vec<u8>);
105
106#[cfg(feature = "image")]
107pub trait RustImage: Sized {
108 fn empty() -> Self;
110
111 fn is_empty(&self) -> bool;
112
113 fn from_path(path: &str) -> Result<Self>;
115
116 fn from_bytes(bytes: &[u8]) -> Result<Self>;
118
119 fn from_dynamic_image(image: DynamicImage) -> Self;
120
121 fn get_size(&self) -> (u32, u32);
123
124 fn thumbnail(&self, width: u32, height: u32) -> Result<Self>;
133
134 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 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 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 self.encode_image(ColorType::Rgb8, ImageFormat::Jpeg)
283 }
284
285 fn to_png(&self) -> Result<RustImageBuffer> {
286 self.encode_image(ColorType::Rgba8, ImageFormat::Png)
288 }
289
290 #[cfg(target_os = "windows")]
291 fn to_bitmap(&self) -> Result<RustImageBuffer> {
292 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}