feat(palette-extract): 加 thumbnail(path, max_edge) 出限尺寸缩略图(M251 · D54)
D54 第 1 条:解码与缩放留在 palette-extract(截图是 PNG,现有 png/jpeg/bmp 解码 feature 已覆盖,零新依赖、零新 crate、零新 feature)。 - 把 finish() 拆成 decode_with_limits()(读 + 嗅探 + 卡解压炸弹)与 fit()(等比缩、 不放大),取色与缩略图共用同一段解码,只是目标边长不同; - 新增 thumbnail(path, max_edge) -> Result<Rgba8, ExtractError>,失败给结构化错误 (Io / Decode),不 panic、不返回灰图;max_edge=0 按 1 处理; - THUMBNAIL_MAX_EDGE = 160(D54 第 5 条)。 用例 5 条:真 PNG 等比缩到给定长边、小图不放大、坏 PNG 给 Decode、缺文件给 Io、 0 边按 1 处理。
This commit is contained in:
1 parent
5487f661d9
commit
798bfb279b
3 files changed
+174
-8
No files matched your search
@@ -64,6 +64,10 @@ use std::path::Path;
|
||||
|
||||
use unlucky_design::palette::{self, ImageInput, ImageSource, Seed};
|
||||
|
||||
mod thumbnail;
|
||||
|
||||
pub use thumbnail::{thumbnail, THUMBNAIL_MAX_EDGE};
|
||||
|
||||
/// 交给 `unlucky_design` 前允许的最大边长(像素)。
|
||||
///
|
||||
/// 256 的依据:量化器自己只采 65536 个点(≈256²),再大的图在 `crates/design` 里也会被
|
||||
@@ -186,31 +190,46 @@ pub fn input<'a>(loaded: &'a Result<Rgba8, ExtractError>) -> ImageInput<'a> {
|
||||
}
|
||||
|
||||
fn finish<R: std::io::BufRead + std::io::Seek>(
|
||||
mut reader: image::ImageReader<R>,
|
||||
reader: image::ImageReader<R>,
|
||||
) -> Result<Rgba8, ExtractError> {
|
||||
Ok(fit(decode_with_limits(reader)?, MAX_EDGE))
|
||||
}
|
||||
|
||||
/// 解码一个 reader(带 [`MAX_SOURCE_EDGE`] 头尺寸上限),**不缩放**。
|
||||
///
|
||||
/// 从 [`finish`] 里拆出来只有一个理由:取色([`load`])与缩略图([`thumbnail`])要**不同的
|
||||
/// 目标边长**,而「读文件 + 嗅探格式 + 卡解压炸弹」这段必须只有一份(两份就会有一份忘了卡上限)。
|
||||
pub(crate) fn decode_with_limits<R: std::io::BufRead + std::io::Seek>(
|
||||
mut reader: image::ImageReader<R>,
|
||||
) -> Result<image::RgbaImage, ExtractError> {
|
||||
let mut limits = image::Limits::default();
|
||||
limits.max_image_width = Some(MAX_SOURCE_EDGE);
|
||||
limits.max_image_height = Some(MAX_SOURCE_EDGE);
|
||||
reader.limits(limits);
|
||||
|
||||
let decoded = reader.decode().map_err(ExtractError::Decode)?;
|
||||
let rgba = decoded.to_rgba8();
|
||||
Ok(decoded.to_rgba8())
|
||||
}
|
||||
|
||||
/// 把解码结果等比缩到**长边 ≤ `max_edge`**(不放大:本来就小的图原样交付)。
|
||||
///
|
||||
/// `thumbnail` 是整数盒式采样:对取色(只关心色彩分布)与缩略图(160px 的墙)都够,
|
||||
/// 且比 Lanczos 快一个量级。
|
||||
pub(crate) fn fit(rgba: image::RgbaImage, max_edge: u32) -> Rgba8 {
|
||||
let (width, height) = (rgba.width(), rgba.height());
|
||||
let scaled = if width.max(height) > MAX_EDGE {
|
||||
// 等比缩到长边 = MAX_EDGE。`thumbnail` 是整数快速采样,对取色(只关心色彩分布)
|
||||
// 足够,且比 Lanczos 快一个量级。
|
||||
let factor = f64::from(MAX_EDGE) / f64::from(width.max(height));
|
||||
let scaled = if width.max(height) > max_edge {
|
||||
let factor = f64::from(max_edge) / f64::from(width.max(height));
|
||||
let target_width = ((f64::from(width) * factor).round() as u32).max(1);
|
||||
let target_height = ((f64::from(height) * factor).round() as u32).max(1);
|
||||
image::imageops::thumbnail(&rgba, target_width, target_height)
|
||||
} else {
|
||||
rgba
|
||||
};
|
||||
Ok(Rgba8 {
|
||||
Rgba8 {
|
||||
width: scaled.width(),
|
||||
height: scaled.height(),
|
||||
pixels: scaled.into_raw(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in new issue
Block a user