image_reducer/utils/
mod.rs1use std::{
2 hash::{BuildHasher, BuildHasherDefault, Hasher},
3 io::Write,
4};
5
6use bytesize::ByteSize;
7use chrono::Local;
8use env_logger::fmt::Formatter;
9use log::{Level, LevelFilter, Record};
10use oxipng::{optimize_from_memory, Options};
11use twox_hash::XxHash64;
12
13use colored::Colorize;
14
15use crate::{errors::TinyError, TinyResult};
16
17pub struct ImageBuffer {
18 pub output: Vec<u8>,
19 pub before: ByteSize,
20 pub after: ByteSize,
21 pub reduce: f64,
22}
23
24pub fn optimize_png(png: &[u8]) -> TinyResult<ImageBuffer> {
25 let opts = Options { ..Options::default() };
26 let image = optimize_from_memory(png, &opts)?;
27 let before = ByteSize::b(png.len() as u64);
28 let after = ByteSize::b(image.len() as u64);
29 let reduce = calc_reduce(png, &image);
30 if is_fully_optimized(png.len(), image.len(), &opts) {
31 return Err(TinyError::ImageOptimized);
32 }
33 Ok(ImageBuffer { output: image, before, after, reduce })
34}
35
36pub fn is_fully_optimized(original_size: usize, optimized_size: usize, opts: &Options) -> bool {
37 original_size <= optimized_size && opts.interlace.is_none()
38}
39
40pub fn calc_reduce(before: &[u8], after: &[u8]) -> f64 {
41 let before = before.len() as f64;
42 let after = after.len() as f64;
43 100.0 * (after - before) / before
44}
45
46pub fn hash_file(image: &[u8]) -> u64 {
47 let mut hasher: XxHash64 = BuildHasherDefault::default().build_hasher();
48 hasher.write(image);
49 hasher.finish()
50}
51
52pub fn logger(level: LevelFilter) {
53 let _ = env_logger::builder()
54 .format_module_path(false)
55 .format(log_writter)
56 .filter(Some("oxipng"), LevelFilter::Off)
57 .filter_level(level)
58 .try_init();
60}
61
62pub fn log_writter(w: &mut Formatter, record: &Record) -> std::io::Result<()> {
63 let header = match record.level() {
64 Level::Error => "Error".red(),
65 Level::Warn => "Warn ".yellow(),
66 Level::Info => "Info ".green(),
67 Level::Debug => "Debug".green(),
68 Level::Trace => "Trace".green(),
69 };
70 let logs = format!("[{header}{}] {}", Local::now().format("%Y-%d-%m %H:%M:%S"), record.args());
71 for (i, line) in logs.lines().enumerate() {
72 if i != 0 {
73 w.write(b"\n")?;
74 w.write(b" ")?;
75 }
76 w.write(line.as_bytes())?;
77 }
78 w.write(b"\n")?;
79 Ok(())
80}