diff --git a/Cargo.lock b/Cargo.lock index c943021..5a95b2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,12 +74,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "askama" version = "0.15.1" @@ -809,12 +803,6 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "either" version = "1.15.0" @@ -981,14 +969,13 @@ dependencies = [ "http", "identicon", "image", - "img-parts", "include_dir", "indexmap", "infer", "jieba-rs", "jiff", + "kamadak-exif", "latex2mathml", - "mozjpeg", "nanoid", "pulldown-cmark", "rand 0.9.2", @@ -1524,17 +1511,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "img-parts" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19734e3c43b2a850f5889c077056e47c874095f2d87e853c7c41214ae67375f0" -dependencies = [ - "bytes", - "crc32fast", - "miniz_oxide", -] - [[package]] name = "include-flate" version = "0.3.1" @@ -1726,6 +1702,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" +dependencies = [ + "mutate_once", +] + [[package]] name = "latex2mathml" version = "0.2.3" @@ -1999,31 +1984,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "mozjpeg" -version = "0.10.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7891b80aaa86097d38d276eb98b3805d6280708c4e0a1e6f6aed9380c51fec9" -dependencies = [ - "arrayvec", - "bytemuck", - "libc", - "mozjpeg-sys", - "rgb", -] - -[[package]] -name = "mozjpeg-sys" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f0dc668bf9bf888c88e2fb1ab16a406d2c380f1d082b20d51dd540ab2aa70c1" -dependencies = [ - "cc", - "dunce", - "libc", - "nasm-rs", -] - [[package]] name = "multer" version = "3.1.0" @@ -2047,6 +2007,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" +[[package]] +name = "mutate_once" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" + [[package]] name = "nanoid" version = "0.4.0" @@ -2056,16 +2022,6 @@ dependencies = [ "rand 0.8.5", ] -[[package]] -name = "nasm-rs" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f676553b60ccbb76f41f9ae8f2428dac3f259ff8f1c2468a174778d06a1af9" -dependencies = [ - "jobserver", - "log", -] - [[package]] name = "new_debug_unreachable" version = "1.0.6" diff --git a/Cargo.toml b/Cargo.toml index 414764b..57c47c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,14 +36,13 @@ image = { version = "0.25.9", default-features = false, features = [ "gif", "webp" ] } -img-parts = "0.4.0" include_dir = "0.7.4" indexmap = "2" infer = "0.19.0" jieba-rs = "0.8.1" jiff = { version = "0.2.15", default-features = false, features = ["std"] } +kamadak-exif = "0.6.1" latex2mathml = "0.2.3" -mozjpeg = "0.10.13" nanoid = "0.4.0" pulldown-cmark = { version = "0.13.0", features = [ "simd", diff --git a/src/controller/upload.rs b/src/controller/upload.rs index 4cf6785..2f4b673 100644 --- a/src/controller/upload.rs +++ b/src/controller/upload.rs @@ -19,12 +19,10 @@ use axum_extra::{ headers::{Cookie, Referer}, }; use data_encoding::HEXLOWER; -use image::{ImageFormat, imageops::FilterType}; -use img_parts::{DynImage, ImageEXIF}; -use mozjpeg::{ColorSpace, Compress, ScanMode}; +use image::{ImageEncoder, ImageFormat, ImageReader, codecs::jpeg::JpegEncoder}; use ring::digest::{Context, SHA1_FOR_LEGACY_USE_ONLY}; use serde::Deserialize; - +use std::io::Cursor; use tokio::fs::{self, remove_file}; use tracing::error; @@ -239,7 +237,11 @@ pub(crate) async fn upload_post( let user_uploads = DB .inner() .open_partition("user_uploads", Default::default())?; - while let Some(field) = multipart.next_field().await.unwrap() { + while let Some(field) = multipart + .next_field() + .await + .map_err(|e| AppError::Custom(e.to_string()))? + { if imgs.len() > 10 { break; } @@ -253,65 +255,38 @@ pub(crate) async fn upload_post( }; let image_format_detected = image::guess_format(&data)?; - let ext; let img_data = match image_format_detected { - ImageFormat::Png | ImageFormat::Jpeg | ImageFormat::WebP => { - if let Ok(Some(mut img)) = DynImage::from_bytes(data) { - img.set_exif(None); - let img_noexif = img.encoder().bytes(); + ImageFormat::Jpeg | ImageFormat::WebP | ImageFormat::Png => { + // Re-encode JPEG/WEBP/PNG if it contains EXIF metadata or size is large + let mut re_encode = false; - // author: "Kim tae hyeon " - // https://github.com/altair823/image_compressor/blob/main/src/compressor.rs - // license = "MIT" - let dyn_img = - image::load_from_memory_with_format(&img_noexif, image_format_detected)?; - let factor = Factor::get(img_noexif.len()); - - // resize - let width = (dyn_img.width() as f32 * factor.size_ratio) as u32; - let height = (dyn_img.width() as f32 * factor.size_ratio) as u32; - let resized_img = dyn_img.resize(width, height, FilterType::Lanczos3); - - // compress - let mut comp = Compress::new(ColorSpace::JCS_RGB); - comp.set_scan_optimization_mode(ScanMode::Auto); - comp.set_quality(factor.quality); - - let target_width = resized_img.width() as usize; - let target_height = resized_img.height() as usize; - comp.set_size(target_width, target_height); - - comp.set_optimize_scans(true); - let mut comp = comp.start_compress(Vec::new()).unwrap(); - - let mut line: usize = 0; - let resized_img_data = resized_img.into_rgb8().into_vec(); - loop { - if line > target_height - 1 { - break; - } - let idx = line * target_width * 3..(line + 1) * target_width * 3; - comp.write_scanlines(&resized_img_data[idx]).unwrap(); - line += 1; + let exifreader = exif::Reader::new(); + if let Ok(exif) = exifreader.read_from_container(&mut Cursor::new(&data)) { + if !exif.buf().is_empty() { + re_encode = true; } + } - if let Ok(comp) = comp.finish() { - ext = *image_format_detected - .extensions_str() - .get(0) - .unwrap_or_else(|| &"jpeg"); - comp - } else { - continue; - } + let quality = Quality::get(data.len()); + if quality < 100 { + re_encode = true; + } + + if re_encode { + let dyn_img = ImageReader::new(std::io::Cursor::new(&data)) + .with_guessed_format()? + .decode()?; + let mut writer = Vec::new(); + let mut encoder = JpegEncoder::new_with_quality(&mut writer, quality); + encoder.set_exif_metadata(vec![]).unwrap(); + dyn_img.write_with_encoder(encoder)?; + writer } else { - continue; + data.to_vec() } } - ImageFormat::Gif => { - ext = "gif"; - data.to_vec() - } + + ImageFormat::Gif => data.to_vec(), _ => { continue; } @@ -321,6 +296,10 @@ pub(crate) async fn upload_post( context.update(&img_data); let digest = context.finish(); let sha1 = HEXLOWER.encode(digest.as_ref()); + let ext = *image_format_detected + .extensions_str() + .get(0) + .unwrap_or_else(|| &"jpg"); let fname = format!("{}.{}", &sha1[0..20], ext); let location = format!("{}/{}", &CONFIG.upload_path, fname); @@ -347,46 +326,18 @@ pub(crate) async fn upload_post( } #[derive(Copy, Clone)] -struct Factor { - /// Quality of the new compressed image. - /// Values range from 0 to 100 in float. - quality: f32, +struct Quality; - /// Ratio for resize the new compressed image. - /// Values range from 0 to 1 in float. - size_ratio: f32, -} - -impl Factor { - /// Create a new `Factor` instance. - /// The `quality` range from 0 to 100 in float, - /// and `size_ratio` range from 0 to 1 in float. - /// - /// # Panics - /// - /// - If the quality value is 0 or less. - /// - If the quality value exceeds 100. - /// - If the size ratio value is 0 or less. - /// - If the size ratio value exceeds 1. - fn new(quality: f32, size_ratio: f32) -> Self { - if (quality > 0. && quality <= 100.) && (size_ratio > 0. && size_ratio <= 1.) { - Self { - quality, - size_ratio, - } - } else { - panic!("Wrong Factor argument!"); - } - } - - fn get(file_size: usize) -> Factor { +impl Quality { + fn get(file_size: usize) -> u8 { match file_size { - file_size if file_size > 5000000 => Factor::new(70., 0.75), - file_size if file_size > 1000000 => Factor::new(75., 0.8), - file_size if file_size > 600000 => Factor::new(80., 0.85), - file_size if file_size > 400000 => Factor::new(85., 0.9), - file_size if file_size > 200000 => Factor::new(90., 0.95), - _ => Factor::new(100., 1.0), + file_size if file_size > 5000000 => 70, + file_size if file_size > 1500000 => 75, + file_size if file_size > 1000000 => 80, + file_size if file_size > 800000 => 85, + file_size if file_size > 600000 => 90, + file_size if file_size > 400000 => 95, + _ => 100, } } }