img_optimize/
encoder.rs

1use std::io::Cursor;
2use cfg_if::cfg_if;
3use crate::structs::*;
4
5pub fn encode_auto_select<'a>(im_info: &ImageInfo, config: &CompressionConfig) -> &'a str {
6    #[allow(unused_mut)] let mut support_webp = true;
7    #[allow(unused_mut)] let mut support_png = true;
8    #[allow(unused_mut)] let mut support_jpeg_xl = true;
9
10    cfg_if! {
11        if #[cfg(not(feature="webp"))] {
12            support_webp = false;
13        }
14    }
15    cfg_if! {
16        if #[cfg(not(feature="png"))] {
17            support_png = false;
18        }
19    }
20    cfg_if! {
21        if #[cfg(not(feature="jpegxl"))] {
22            support_jpeg_xl = false;
23        }
24    }
25
26    match im_info.pixels {
27        ImagePixels::U8(_) => {
28            if support_webp && is_webp_supported_image(&im_info, &config) {
29                // If 8-bit color and RGB or RGBA, use webp lossless
30                "webp"
31            }
32            else if support_png && is_png_supported_image(&im_info, &config) {
33                "png"
34            }
35            else
36            {
37                ""
38            }
39        },
40        ImagePixels::U16(_) => {
41            if support_png && is_png_supported_image(&im_info, &config) {
42                "png"
43            }
44            else
45            {
46                ""
47            }
48        },
49        _ => {
50            ""
51        }
52    }
53}
54
55// Returns: (file_extension, file_content)
56pub fn encode_auto(im_info: ImageInfo, config: CompressionConfig) -> (String, Vec<u8>) {
57    let decide = encode_auto_select(&im_info, &config);
58
59    match decide {
60        #[cfg(feature="webp")]
61        "webp" => {
62            let webp_memory = encode_lossless_webp(im_info);
63            ("webp".to_owned(), webp_memory)
64        },
65        #[cfg(feature="png")]
66        "png" => {
67            let png_memory = encode_png(im_info, config);
68            ("png".to_owned(), png_memory)
69        },
70        _ => {
71            panic!("Unsupported image format");
72        }
73    }
74}
75
76fn is_webp_supported_image(im_info: &ImageInfo, config: &CompressionConfig) -> bool {
77    config.allow_webp &&
78        (im_info.bit_depth == 8) &&
79        (im_info.color_type == 2 || im_info.color_type == 6)
80}
81
82fn is_png_supported_image(im_info: &ImageInfo, config: &CompressionConfig) -> bool {
83    config.allow_png &&
84        (im_info.bit_depth == 1 || im_info.bit_depth == 2 || im_info.bit_depth == 4 || im_info.bit_depth == 8 || im_info.bit_depth == 16) &&
85        (im_info.color_type == 0 || im_info.color_type == 2 || /* im_info.color_type == 3 || */ im_info.color_type == 4 || im_info.color_type == 6)
86}
87
88#[cfg(feature="webp")]
89pub fn encode_lossless_webp(im_info: ImageInfo) -> Vec<u8> {
90    let bit_per_pixel = match im_info.color_type {
91        2 => 3,
92        6 => 4,
93        _ => panic!("Unsupported color type"),
94    };
95
96    let pixel_layout = match im_info.color_type {
97        2 => webp::PixelLayout::Rgb,
98        6 => webp::PixelLayout::Rgba,
99        _ => panic!("Unsupported color type"),
100    };
101
102    let mut image_pixels = vec![0; im_info.width as usize * im_info.height as usize * bit_per_pixel];
103
104    match im_info.pixels {
105        ImagePixels::U8(pixels) => {
106            for i in 0..pixels.len() {
107                image_pixels[i] = pixels[i];
108            }
109        },
110        _ => panic!("Unsupported pixel type"),
111    }
112
113    let encoder = webp::Encoder::new(&*image_pixels, pixel_layout, im_info.width, im_info.height);
114
115    // ToDo: Add lossy support
116    let webp_memory = encoder.encode_lossless();
117
118    webp_memory.to_vec()
119}
120
121#[cfg(feature="png")]
122pub fn encode_png(im_info: ImageInfo, compression_config: CompressionConfig) -> Vec<u8> {
123    cfg_if! {
124        if #[cfg(feature="oxipng")] {
125            return encode_png_oxipng(im_info, compression_config);
126        } else {
127            return encode_png_image_rs(im_info);
128        }
129    }
130}
131
132#[cfg(feature="png")]
133pub fn encode_png_image_rs(im_info: ImageInfo) -> Vec<u8> {
134    let mut output_vec: Vec<u8> = Vec::new();
135    {
136        let output_cursor = Cursor::new(&mut output_vec);
137
138        let mut encoder = png::Encoder::new(output_cursor, im_info.width, im_info.height);
139
140        let color_type = match im_info.color_type {
141            0 => png::ColorType::Grayscale,
142            2 => png::ColorType::Rgb,
143            3 => png::ColorType::Indexed,
144            4 => png::ColorType::GrayscaleAlpha,
145            6 => png::ColorType::Rgba,
146            _ => panic!("Unsupported color type"),
147        };
148
149        encoder.set_color(color_type);
150
151        let color_depth = match im_info.bit_depth {
152            1 => png::BitDepth::One,
153            2 => png::BitDepth::Two,
154            4 => png::BitDepth::Four,
155            8 => png::BitDepth::Eight,
156            16 => png::BitDepth::Sixteen,
157            _ => panic!("Unsupported bit_depth"),
158        };
159
160        encoder.set_depth(color_depth);
161
162        let mut pixels;
163
164        match im_info.pixels {
165            ImagePixels::U8(pxl) => {
166                pixels = pxl;
167            },
168            ImagePixels::U16(pxl) => {
169                pixels = Vec::new();
170                for i in 0..pxl.len() {
171                    pixels.push((pxl[i] >> 8) as u8);
172                    pixels.push((pxl[i] & 0xFF) as u8);
173                }
174            },
175            _ => panic!("Unsupported pixel type"),
176        }
177
178        let mut writer = encoder.write_header().unwrap();
179
180        // ToDo: This might be not support 16-bit image.
181        writer.write_image_data(&*pixels).unwrap();
182    }
183
184    let mut output: Vec<u8> = vec![0; output_vec.len()];
185    for i in 0..output.len() {
186        output[i] = (&*output_vec)[i];
187    }
188    output
189}
190
191#[cfg(feature="oxipng")]
192pub fn encode_png_oxipng(im_info: ImageInfo, compression_config: CompressionConfig) -> Vec<u8> {
193    let color_type = match im_info.color_type {
194        0 => oxipng::ColorType::Grayscale {
195            transparent_shade: None,
196        },
197        2 => oxipng::ColorType::RGB {
198            transparent_color: None,
199        },
200        4 => oxipng::ColorType::GrayscaleAlpha,
201        6 => oxipng::ColorType::RGBA,
202        _ => panic!("Unsupported color type"),
203    };
204
205    let bit_depth = match im_info.bit_depth {
206        1 => oxipng::BitDepth::One,
207        2 => oxipng::BitDepth::Two,
208        4 => oxipng::BitDepth::Four,
209        8 => oxipng::BitDepth::Eight,
210        16 => oxipng::BitDepth::Sixteen,
211        _ => panic!("Unsupported bit depth"),
212    };
213
214    let pixels;
215    match im_info.pixels {
216        ImagePixels::U8(pixels8) => {
217            pixels = pixels8;
218        },
219        ImagePixels::U16(pixels16) => {
220            let mut pixels8 = vec![0; pixels16.len() * 2];
221            for i in 0..pixels16.len() {
222                pixels8[i * 2] = (pixels16[i] >> 8) as u8;
223                pixels8[i * 2 + 1] = pixels16[i] as u8;
224            }
225            pixels = pixels8;
226        },
227        _ => panic!("Unsupported pixel type"),
228    }
229
230    let oxipng_img = oxipng::RawImage::new(
231        im_info.width,
232        im_info.height,
233        color_type,
234        bit_depth,
235        pixels,
236    ).unwrap();
237
238    let mut options = oxipng::Options::max_compression();
239    if compression_config.allow_oxipng_zopfli {
240        options.deflate = oxipng::Deflaters::Zopfli {
241            iterations: std::num::NonZeroU8::new(15).unwrap(), // This is default of oxipng cli.
242        };
243    }
244
245    let optimized = oxipng_img.create_optimized_png(&options).unwrap();
246    optimized
247}