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
@@ -0,0 +1,56 @@
|
||||
//! 缩略图:路径 → 限尺寸的 [`Rgba8`](D54 第 1 条,M251 · SAVESHOTS)。
|
||||
//!
|
||||
//! ## 为什么落在本 crate
|
||||
//!
|
||||
//! D54 定的分层没有变:图片解码**只在本 crate**(`crates/design` 零第三方依赖的代价就是把解码
|
||||
//! 摊到这里)。截图是 PNG,本 crate 的 `image` 已经开了 `png`/`jpeg`/`bmp` 解码 feature,
|
||||
//! 所以加这条路**零新依赖、零新 crate、零新 feature**——只是把「解码 + 限尺寸」这段已有的能力
|
||||
//! 换个目标边长([`THUMBNAIL_MAX_EDGE`])暴露一次。
|
||||
//!
|
||||
//! ## 不落盘
|
||||
//!
|
||||
//! 这里**只返回像素**,不写任何缓存文件(D54 第 2 条):写 PNG 需要编码器,而编码器只为一份
|
||||
//! 磁盘缓存引入不划算;内存缓存归调用方(presenter 的 `host::saves::thumbs`)。
|
||||
//!
|
||||
//! ## 失败不 panic
|
||||
//!
|
||||
//! 读不到 / 不是图片 / 头里写着 40000×40000,全都变成 [`ExtractError`](与取色那条路同一族
|
||||
//! 错误)。调用方据此给「逐条可见」的失败原因——**不许**在这里「读不到就画个灰块」。
|
||||
//!
|
||||
//! 与 [`crate::load`] 的区别只有一条:目标边长。`load` 钉 `MAX_EDGE = 256`(取色只需要色彩分布),
|
||||
//! 这里由调用方给(缩略图墙用 160)。
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use unlucky_palette_extract::{thumbnail, THUMBNAIL_MAX_EDGE};
|
||||
//!
|
||||
//! match thumbnail("screenshots/2024-05-01_10.00.00.png", THUMBNAIL_MAX_EDGE) {
|
||||
//! Ok(thumb) => println!("{}×{}", thumb.width(), thumb.height()),
|
||||
//! Err(error) => eprintln!("这张图没有缩略图:{error}"),
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{decode_with_limits, fit, ExtractError, Rgba8};
|
||||
|
||||
/// 缩略图的最大边(像素)。D54 定 160。
|
||||
///
|
||||
/// 依据:一屏缩略图墙按 4 列排、详情列宽约 700px ⇒ 每格 ≈ 160px。再大只是多占内存
|
||||
/// (160² × 4B ≈ 100KB/张,60 张 ≈ 6MB),肉眼在墙上看不出差别。
|
||||
pub const THUMBNAIL_MAX_EDGE: u32 = 160;
|
||||
|
||||
/// 从文件路径解码并缩到**长边 ≤ `max_edge`**(等比、不放大),返回 RGBA8 像素。
|
||||
///
|
||||
/// 格式按**魔数**嗅探,不看扩展名(与 [`crate::load`] 同一条路:用户把 `.jpg` 改名成 `.png`
|
||||
/// 也照样读得出来)。`max_edge = 0` 按 1 处理(防调用方传 0 得到 0×0 的图)。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - [`ExtractError::Io`]:文件不存在 / 无权限 / 被占用 / 打不开;
|
||||
/// - [`ExtractError::Decode`]:不是认得的图片、文件截断、或头尺寸超过 16384(解压炸弹)。
|
||||
pub fn thumbnail(path: impl AsRef<Path>, max_edge: u32) -> Result<Rgba8, ExtractError> {
|
||||
let max_edge = max_edge.max(1);
|
||||
let reader = image::ImageReader::open(path).map_err(ExtractError::Io)?;
|
||||
let reader = reader.with_guessed_format().map_err(ExtractError::Io)?;
|
||||
Ok(fit(decode_with_limits(reader)?, max_edge))
|
||||
}
|
||||
Reference in new issue
Block a user