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