mirror of
https://github.com/freedit-org/freedit.git
synced 2026-01-09 12:38:04 -05:00
fix #395 Re-encode JPEG/WEBP/PNG if it contains EXIF metadata or size is large
This commit is contained in:
76
Cargo.lock
generated
76
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <kimth0734@gmail.com>"
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user