caesium/
lib.rs

1extern crate alloc;
2
3use std::fs;
4use std::fs::File;
5use std::io::Write;
6
7use crate::parameters::TiffCompression::{Deflate, Lzw, Packbits};
8use crate::parameters::{CSParameters, TiffDeflateLevel};
9use crate::utils::{get_filetype_from_memory, get_filetype_from_path};
10use error::CaesiumError;
11
12mod convert;
13pub mod error;
14#[cfg(feature = "gif")]
15mod gif;
16mod interface;
17#[cfg(feature = "jpg")]
18mod jpeg;
19pub mod parameters;
20#[cfg(feature = "png")]
21mod png;
22mod resize;
23#[cfg(feature = "tiff")]
24mod tiff;
25mod utils;
26#[cfg(feature = "webp")]
27mod webp;
28
29/// Compresses an image file from the input path and writes the compressed image to the output path.
30///
31/// # Arguments
32///
33/// * `input_path` - A string representing the path to the input image file.
34/// * `output_path` - A string representing the path to the output compressed image file.
35/// * `parameters` - A reference to `CSParameters` containing compression settings.
36///
37/// # Returns
38///
39/// * `Result<(), CaesiumError>` - Returns `Ok(())` if compression is successful, otherwise returns a `CaesiumError`.
40pub fn compress(input_path: String, output_path: String, parameters: &CSParameters) -> error::Result<()> {
41    validate_parameters(parameters)?;
42    let file_type = get_filetype_from_path(&input_path);
43
44    match file_type {
45        #[cfg(feature = "jpg")]
46        SupportedFileTypes::Jpeg => {
47            jpeg::compress(input_path, output_path, parameters)?;
48        }
49        #[cfg(feature = "png")]
50        SupportedFileTypes::Png => {
51            png::compress(input_path, output_path, parameters)?;
52        }
53        #[cfg(feature = "webp")]
54        SupportedFileTypes::WebP => {
55            webp::compress(input_path, output_path, parameters)?;
56        }
57        #[cfg(feature = "gif")]
58        SupportedFileTypes::Gif => {
59            gif::compress(input_path, output_path, parameters)?;
60        }
61        #[cfg(feature = "tiff")]
62        SupportedFileTypes::Tiff => {
63            tiff::compress(input_path, output_path, parameters)?;
64        }
65        _ => {
66            return Err(CaesiumError {
67                message: "Unknown file type or file not found".into(),
68                code: 10000,
69            });
70        }
71    }
72
73    Ok(())
74}
75
76/// Compresses an image file in memory and returns the compressed image as a byte vector.
77///
78/// # Arguments
79///
80/// * `in_file` - A vector of bytes representing the input image file.
81/// * `parameters` - A reference to `CSParameters` containing compression settings.
82///
83/// # Returns
84///
85/// * `Result<Vec<u8>, CaesiumError>` - Returns a vector of bytes representing the compressed image if successful, otherwise returns a `CaesiumError`.
86pub fn compress_in_memory(in_file: Vec<u8>, parameters: &CSParameters) -> error::Result<Vec<u8>> {
87    let file_type = get_filetype_from_memory(in_file.as_slice());
88    let compressed_file = match file_type {
89        #[cfg(feature = "jpg")]
90        SupportedFileTypes::Jpeg => jpeg::compress_in_memory(in_file, parameters)?,
91        #[cfg(feature = "png")]
92        SupportedFileTypes::Png => png::compress_in_memory(in_file, parameters)?,
93        #[cfg(feature = "gif")]
94        SupportedFileTypes::Gif => gif::compress_in_memory(in_file, parameters)?,
95        #[cfg(feature = "webp")]
96        SupportedFileTypes::WebP => webp::compress_in_memory(in_file, parameters)?,
97        #[cfg(feature = "tiff")]
98        SupportedFileTypes::Tiff => tiff::compress_in_memory(in_file, parameters)?,
99        _ => {
100            return Err(CaesiumError {
101                message: "Format not supported for compression in memory".into(),
102                code: 10200,
103            });
104        }
105    };
106
107    Ok(compressed_file)
108}
109
110/// Compresses an image file in memory up to a specified size and returns the compressed image as a byte vector.
111///
112/// # Arguments
113///
114/// * `in_file` - A vector of bytes representing the input image file.
115/// * `parameters` - A mutable reference to `CSParameters` containing compression settings.
116/// * `max_output_size` - The maximum size of the output compressed image in bytes.
117/// * `return_smallest` - A boolean indicating whether to return the smallest compressed image if the desired size is not achieved.
118///
119/// # Returns
120///
121/// * `Result<Vec<u8>, CaesiumError>` - Returns a vector of bytes representing the compressed image if successful, otherwise returns a `CaesiumError`.
122pub fn compress_to_size_in_memory(
123    in_file: Vec<u8>,
124    parameters: &mut CSParameters,
125    max_output_size: usize,
126    return_smallest: bool,
127) -> error::Result<Vec<u8>> {
128    let file_type = get_filetype_from_memory(&in_file);
129
130    let tolerance_percentage = 2;
131    let tolerance = max_output_size * tolerance_percentage / 100;
132    let mut quality = 80;
133    let mut last_less = 0;
134    let mut last_high = 101;
135    let max_tries: u32 = 10;
136    let mut tries: u32 = 0;
137
138    let compressed_file = match file_type {
139        #[cfg(feature = "tiff")]
140        SupportedFileTypes::Tiff => {
141            let algorithms = [Lzw, Packbits];
142            parameters.tiff.deflate_level = TiffDeflateLevel::Best;
143            parameters.tiff.algorithm = Deflate;
144            let mut smallest_result = tiff::compress_in_memory(in_file.clone(), parameters)?; //TODO clone
145            for tc in algorithms {
146                parameters.tiff.algorithm = tc;
147                let result = tiff::compress_in_memory(in_file.clone(), parameters)?; //TODO clone
148                if result.len() < smallest_result.len() {
149                    smallest_result = result;
150                }
151            }
152            return if return_smallest || smallest_result.len() <= max_output_size {
153                Ok(smallest_result)
154            } else {
155                Err(CaesiumError {
156                    message: "Cannot compress to desired quality".into(),
157                    code: 10202,
158                })
159            };
160        }
161        _ => loop {
162            if tries >= max_tries {
163                return Err(CaesiumError {
164                    message: "Max tries reached".into(),
165                    code: 10201,
166                });
167            }
168
169            let compressed_file = match file_type {
170                #[cfg(feature = "jpg")]
171                SupportedFileTypes::Jpeg => {
172                    parameters.jpeg.quality = quality;
173                    jpeg::compress_in_memory(in_file.clone(), parameters)? //TODO clone
174                }
175                #[cfg(feature = "png")]
176                SupportedFileTypes::Png => {
177                    parameters.png.quality = quality;
178                    png::compress_in_memory(in_file.clone(), parameters)? //TODO clone
179                }
180                #[cfg(feature = "gif")]
181                SupportedFileTypes::Gif => {
182                    parameters.gif.quality = quality;
183                    gif::compress_in_memory(in_file.clone(), parameters)? //TODO clone
184                }
185                #[cfg(feature = "webp")]
186                SupportedFileTypes::WebP => {
187                    parameters.webp.quality = quality;
188                    webp::compress_in_memory(in_file.clone(), parameters)? //TODO clone
189                }
190                _ => {
191                    return Err(CaesiumError {
192                        message: "Format not supported for compression to size".into(),
193                        code: 10200,
194                    });
195                }
196            };
197
198            let compressed_file_size = compressed_file.len();
199
200            if compressed_file_size <= max_output_size && max_output_size - compressed_file_size < tolerance {
201                break compressed_file;
202            }
203
204            if compressed_file_size <= max_output_size {
205                last_less = quality;
206            } else {
207                last_high = quality;
208            }
209            let last_quality = quality;
210            quality = ((last_high + last_less) / 2).clamp(1, 100);
211            if last_quality == quality {
212                if quality == 1 && last_high == 1 {
213                    return if return_smallest {
214                        Ok(compressed_file)
215                    } else {
216                        Err(CaesiumError {
217                            message: "Cannot compress to desired quality".into(),
218                            code: 10202,
219                        })
220                    };
221                }
222
223                break compressed_file;
224            }
225
226            tries += 1;
227        },
228    };
229
230    Ok(compressed_file)
231}
232
233/// Compresses an image file from the input path up to a specified size and writes the compressed image to the output path.
234///
235/// # Arguments
236///
237/// * `input_path` - A string representing the path to the input image file.
238/// * `output_path` - A string representing the path to the output compressed image file.
239/// * `parameters` - A mutable reference to `CSParameters` containing compression settings.
240/// * `max_output_size` - The maximum size of the output compressed image in bytes.
241/// * `return_smallest` - A boolean indicating whether to return the smallest compressed image if the desired size is not achieved.
242///
243/// # Returns
244///
245/// * `Result<(), CaesiumError>` - Returns `Ok(())` if compression is successful, otherwise returns a `CaesiumError`.
246pub fn compress_to_size(
247    input_path: String,
248    output_path: String,
249    parameters: &mut CSParameters,
250    max_output_size: usize,
251    return_smallest: bool,
252) -> error::Result<()> {
253    let in_file = fs::read(input_path.clone()).map_err(|e| CaesiumError {
254        message: e.to_string(),
255        code: 10201,
256    })?;
257    let original_size = in_file.len();
258
259    // If we resize, we should always go for at least a round of compression
260    if !(parameters.width > 0 || parameters.height > 0) && original_size <= max_output_size {
261        fs::copy(input_path, output_path).map_err(|e| CaesiumError {
262            message: e.to_string(),
263            code: 10202,
264        })?;
265        return Ok(());
266    }
267
268    let compressed_file = compress_to_size_in_memory(in_file, parameters, max_output_size, return_smallest)?;
269    let mut out_file = File::create(output_path).map_err(|e| CaesiumError {
270        message: e.to_string(),
271        code: 10203,
272    })?;
273    out_file.write_all(&compressed_file).map_err(|e| CaesiumError {
274        message: e.to_string(),
275        code: 10204,
276    })?;
277
278    Ok(())
279}
280
281/// Converts an image file from the input path to a specified format and writes the converted image to the output path.
282///
283/// # Arguments
284///
285/// * `input_path` - A string representing the path to the input image file.
286/// * `output_path` - A string representing the path to the output converted image file.
287/// * `parameters` - A reference to `CSParameters` containing conversion settings.
288/// * `format` - The target format to convert the image to.
289///
290/// # Returns
291///
292/// * `Result<(), CaesiumError>` - Returns `Ok(())` if conversion is successful, otherwise returns a `CaesiumError`.
293pub fn convert(
294    input_path: String,
295    output_path: String,
296    parameters: &CSParameters,
297    format: SupportedFileTypes,
298) -> error::Result<()> {
299    let file_type = get_filetype_from_path(&input_path);
300
301    if file_type == format {
302        return Err(CaesiumError {
303            message: "Cannot convert to the same format".into(),
304            code: 10406,
305        });
306    }
307
308    let in_file = fs::read(input_path).map_err(|e| CaesiumError {
309        message: e.to_string(),
310        code: 10410,
311    })?;
312    let output_buffer = convert_in_memory(in_file, parameters, format).map_err(|e| CaesiumError {
313        message: e.to_string(),
314        code: 10411,
315    })?;
316
317    let mut out_file = File::create(output_path).map_err(|e| CaesiumError {
318        message: e.to_string(),
319        code: 10412,
320    })?;
321
322    out_file.write_all(&output_buffer).map_err(|e| CaesiumError {
323        message: e.to_string(),
324        code: 10413,
325    })?;
326
327    Ok(())
328}
329
330/// Converts an image file in memory to a specified format and returns the converted image as a byte vector.
331///
332/// # Arguments
333///
334/// * `in_file` - A vector of bytes representing the input image file.
335/// * `parameters` - A reference to `CSParameters` containing conversion settings.
336/// * `format` - The target format to convert the image to.
337///
338/// # Returns
339///
340/// * `Result<Vec<u8>, CaesiumError>` - Returns a vector of bytes representing the converted image if successful, otherwise returns a `CaesiumError`.
341pub fn convert_in_memory(
342    in_file: Vec<u8>,
343    parameters: &CSParameters,
344    format: SupportedFileTypes,
345) -> Result<Vec<u8>, CaesiumError> {
346    convert::convert_in_memory(in_file, format, parameters)
347}
348
349fn validate_parameters(parameters: &CSParameters) -> error::Result<()> {
350    if parameters.jpeg.quality > 100 {
351        return Err(CaesiumError {
352            message: "Invalid JPEG quality value".into(),
353            code: 10001,
354        });
355    }
356
357    if parameters.png.quality > 100 {
358        return Err(CaesiumError {
359            message: "Invalid PNG quality value".into(),
360            code: 10002,
361        });
362    }
363
364    if parameters.png.optimization_level > 6 {
365        return Err(CaesiumError {
366            message: "Invalid PNG optimization level".into(),
367            code: 10006,
368        });
369    }
370
371    if parameters.gif.quality > 100 || parameters.gif.quality < 1 {
372        return Err(CaesiumError {
373            message: "Invalid GIF quality value".into(),
374            code: 10003,
375        });
376    }
377
378    if parameters.webp.quality > 100 {
379        return Err(CaesiumError {
380            message: "Invalid WebP quality value".into(),
381            code: 10004,
382        });
383    }
384
385    Ok(())
386}
387
388#[repr(C)]
389#[derive(PartialEq, Eq, Clone, Copy)]
390pub enum SupportedFileTypes {
391    Jpeg,
392    Png,
393    Gif,
394    WebP,
395    Tiff,
396    Unkn,
397}