1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
use image::imageops::FilterType;
use image::{self, DynamicImage, GenericImageView};
use std::error::Error;
use std::io::Cursor;
pub type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync + 'static>>;
pub type CallBack = Box<dyn Fn() + Send + Sync>;

pub trait ContentData {
    fn get_format(&self) -> ContentFormat;

    fn as_bytes(&self) -> &[u8];

    fn as_str(&self) -> Result<&str>;
}

pub enum ClipboardContent {
    Text(String),
    Rtf(String),
    Html(String),
    Image(RustImageData),
    Files(Vec<String>),
    Other(String, Vec<u8>),
}

impl ContentData for ClipboardContent {
    fn get_format(&self) -> ContentFormat {
        match self {
            ClipboardContent::Text(_) => ContentFormat::Text,
            ClipboardContent::Rtf(_) => ContentFormat::Rtf,
            ClipboardContent::Html(_) => ContentFormat::Html,
            ClipboardContent::Image(_) => ContentFormat::Image,
            ClipboardContent::Files(_) => ContentFormat::Files,
            ClipboardContent::Other(format, _) => ContentFormat::Other(format.clone()),
        }
    }

    fn as_bytes(&self) -> &[u8] {
        match self {
            ClipboardContent::Text(data) => data.as_bytes(),
            ClipboardContent::Rtf(data) => data.as_bytes(),
            ClipboardContent::Html(data) => data.as_bytes(),
            ClipboardContent::Image(data) => data.as_bytes(),
            ClipboardContent::Files(data) => {
                // use first file path as data
                if let Some(path) = data.first() {
                    path.as_bytes()
                } else {
                    &[]
                }
            }
            ClipboardContent::Other(_, data) => data.as_slice(),
        }
    }

    fn as_str(&self) -> Result<&str> {
        match self {
            ClipboardContent::Text(data) => Ok(data),
            ClipboardContent::Rtf(data) => Ok(data),
            ClipboardContent::Html(data) => Ok(data),
            ClipboardContent::Image(_) => Err("can't convert image to string".into()),
            ClipboardContent::Files(data) => {
                // use first file path as data
                if let Some(path) = data.first() {
                    Ok(path)
                } else {
                    Err("content is empty".into())
                }
            }
            ClipboardContent::Other(_, data) => std::str::from_utf8(data).map_err(|e| e.into()),
        }
    }
}

#[derive(Clone)]
pub enum ContentFormat {
    Text,
    Rtf,
    Html,
    Image,
    Files,
    Other(String),
}

pub struct RustImageData {
    width: u32,
    height: u32,
    data: Option<DynamicImage>,
}

/// 此处的 RustImageBuffer 已经是带有图片格式的字节流,例如 png,jpeg;
pub struct RustImageBuffer(Vec<u8>);

impl RustImageData {
    pub fn as_bytes(&self) -> &[u8] {
        match &self.data {
            Some(image) => image.as_bytes(),
            None => &[],
        }
    }
}

pub trait RustImage: Sized {
    /// create an empty image
    fn empty() -> Self;

    fn is_empty(&self) -> bool;

    /// Read image from file path
    fn from_path(path: &str) -> Result<Self>;

    /// Create a new image from a byte slice
    fn from_bytes(bytes: &[u8]) -> Result<Self>;

    /// width and height
    fn get_size(&self) -> (u32, u32);

    /// Scale this image down to fit within a specific size.
    /// Returns a new image. The image's aspect ratio is preserved.
    /// The image is scaled to the maximum possible size that fits
    /// within the bounds specified by `nwidth` and `nheight`.
    ///
    /// This method uses a fast integer algorithm where each source
    /// pixel contributes to exactly one target pixel.
    /// May give aliasing artifacts if new size is close to old size.
    fn thumbnail(&self, width: u32, height: u32) -> Result<Self>;

    /// en: Adjust the size of the image without retaining the aspect ratio
    /// zh: 调整图片大小,不保留长宽比
    fn resize(&self, width: u32, height: u32, filter: FilterType) -> Result<Self>;

    /// en: Convert image to jpeg format, quality is the quality, give a value of 0-100, 100 is the highest quality,
    /// the returned image is a new image, and the data itself will not be modified
    /// zh: 把图片转为 jpeg 格式,quality(0-100) 为质量,输出字节数组,可直接通过 io 写入文件
    fn to_jpeg(&self, quality: u8) -> Result<RustImageBuffer>;

    /// en: Convert to png format, the returned image is a new image, and the data itself will not be modified
    /// zh: 转为 png 格式,返回的为新的图片,本身数据不会修改
    fn to_png(&self) -> Result<RustImageBuffer>;

    fn to_bitmap(&self) -> Result<RustImageBuffer>;

    fn save_to_path(&self, path: &str) -> Result<()>;
}

impl RustImage for RustImageData {
    fn empty() -> Self {
        RustImageData {
            width: 0,
            height: 0,
            data: None,
        }
    }

    fn is_empty(&self) -> bool {
        self.data.is_none()
    }

    fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let image = image::load_from_memory(bytes)?;
        let (width, height) = image.dimensions();
        Ok(RustImageData {
            width,
            height,
            data: Some(image),
        })
    }

    fn from_path(path: &str) -> Result<Self> {
        let image = image::open(path)?;
        let (width, height) = image.dimensions();
        Ok(RustImageData {
            width,
            height,
            data: Some(image),
        })
    }

    fn get_size(&self) -> (u32, u32) {
        (self.width, self.height)
    }

    fn resize(&self, width: u32, height: u32, filter: FilterType) -> Result<Self> {
        match &self.data {
            Some(image) => {
                let resized = image.resize_exact(width, height, filter);
                Ok(RustImageData {
                    width: resized.width(),
                    height: resized.height(),
                    data: Some(resized),
                })
            }
            None => Err("image is empty".into()),
        }
    }

    fn to_jpeg(&self, quality: u8) -> Result<RustImageBuffer> {
        match &self.data {
            Some(image) => {
                let mut buf = Cursor::new(Vec::new());
                image.write_to(&mut buf, image::ImageOutputFormat::Jpeg(quality))?;
                Ok(RustImageBuffer(buf.into_inner()))
            }
            None => Err("image is empty".into()),
        }
    }

    fn save_to_path(&self, path: &str) -> Result<()> {
        match &self.data {
            Some(image) => {
                image.save(path)?;
                Ok(())
            }
            None => Err("image is empty".into()),
        }
    }

    fn to_png(&self) -> Result<RustImageBuffer> {
        match &self.data {
            Some(image) => {
                let mut buf = Cursor::new(Vec::new());
                image.write_to(&mut buf, image::ImageOutputFormat::Png)?;
                Ok(RustImageBuffer(buf.into_inner()))
            }
            None => Err("image is empty".into()),
        }
    }

    fn thumbnail(&self, width: u32, height: u32) -> Result<Self> {
        match &self.data {
            Some(image) => {
                let resized = image.thumbnail(width, height);
                Ok(RustImageData {
                    width: resized.width(),
                    height: resized.height(),
                    data: Some(resized),
                })
            }
            None => Err("image is empty".into()),
        }
    }

    fn to_bitmap(&self) -> Result<RustImageBuffer> {
        match &self.data {
            Some(image) => {
                let mut buf = Cursor::new(Vec::new());
                image.write_to(&mut buf, image::ImageOutputFormat::Bmp)?;
                Ok(RustImageBuffer(buf.into_inner()))
            }
            None => Err("image is empty".into()),
        }
    }
}

impl RustImageBuffer {
    pub fn get_bytes(&self) -> &[u8] {
        &self.0
    }

    pub fn save_to_path(&self, path: &str) -> Result<()> {
        std::fs::write(path, &self.0)?;
        Ok(())
    }
}