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
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 enum ContentFormat<'a> {
    Text,
    Rtf,
    Html,
    Image,
    Other(&'a str),
}

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

pub struct RustImageBuffer(Vec<u8>);

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(())
    }
}