caesium/
lib.rs

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