封面由 AI 生成,仅作主题示意。
封面由 AI 生成,仅作主题示意。
图片制作完成以后,运营仍可能收到尺寸过小、体积过大,甚至扩展名与内容不相符的文件。本文实现一道本地 PNG 预检:读取文件签名和 IHDR 宽高,按项目配置输出告警,不修改原图,不上传图片。
它适合接在素材导出之后。例如,从 Picset 或其他图像工具导出的文件,可以先进入这道检查;这不需要假设这些工具提供 API,也不是对其输出质量的评价。[1] JPEG、WebP 等格式不在这段代码的识别范围内。
AI 技术插图,仅解释检查对象,不是程序运行结果。
先划清这段检查的边界
PNG 以固定签名开始,首个数据块为 IHDR,其中包含宽、高等信息。[2] 根据这些字段,可以判断基本头部是否符合预期,以及尺寸是否达到当前配置。
但读取头部不等于成功解码。下面代码不校验所有数据块的 CRC,也不检查图像数据是否完整,所以不应把返回结果命名为“图片安全”或“可上架”。输出只说明这一轮文件预检看到了什么。图像真正打开后的内容、商品准确性和目标平台要求,还需要其他检查。
读取有限字节,不把大文件全部装进内存
代码使用 Node.js 内置模块,只读取前面一小段头部。下面的尺寸与体积阈值均为演示配置,不是任何电商平台的统一要求。正式使用时,应按真实版位填写。
const fs = require('node:fs/promises');
const path = require('node:path');
const SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
const rules = { minWidth: 1200, minHeight: 1200,
maxBytes: 5 * 1024 * 1024 };
function inspectHeader(head, bytes, config) {
if (head.length < 33) throw new Error('PNG 头部不足');
if (!head.subarray(0, 8).equals(SIGNATURE))
throw new Error('文件签名不是 PNG');
if (head.readUInt32BE(8) !== 13 ||
head.toString('ascii', 12, 16) !== 'IHDR')
throw new Error('首个块不是预期的 IHDR');
const width = head.readUInt32BE(16);
const height = head.readUInt32BE(20);
if (!width || !height || width > 0x7fffffff ||
height > 0x7fffffff) throw new Error('宽高超出 PNG 范围');
const warnings = [];
if (width < config.minWidth || height < config.minHeight)
warnings.push('宽高低于项目阈值');
if (bytes > config.maxBytes) warnings.push('体积超过项目阈值');
return { width, height, bytes, warnings };
}
async function inspectFile(file) {
const handle = await fs.open(file, 'r');
try {
const stat = await handle.stat();
if (!stat.isFile()) throw new Error('不是普通文件');
const head = Buffer.alloc(33);
const { bytesRead } = await handle.read(head, 0, 33, 0);
return inspectHeader(head.subarray(0, bytesRead), stat.size, rules);
} finally { await handle.close(); }
}
async function scan(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isFile() || !/\.png$/i.test(entry.name)) continue;
try { console.log(JSON.stringify({ file: entry.name,
...await inspectFile(path.join(dir, entry.name)) })); }
catch (error) { console.log(JSON.stringify({
file: entry.name, error: error.message })); }
}
}
module.exports = { inspectHeader, inspectFile, scan };
if (require.main === module)
scan(process.argv[2] || '.').catch(e => {
console.error(e.message); process.exitCode = 1;
});
保存为 png-preflight.cjs,执行 node png-preflight.cjs ./images。每个被扫描的文件输出一行 JSON。没有告警意味着“没有违反这份配置”,不是达到了全部平台要求;不支持的扩展名会被跳过,代码也不会递归扫描子目录或跟随目录中的符号链接。
让失败路径也能复查
测试时不能只用一张正常图片。应包含短文件、改了扩展名的非 PNG、错误 IHDR、零宽高,以及尺寸或体积触发告警的输入。文章包附有使用合成头部的单元测试,并额外对本批配图运行预检。合成输入只能验证这些分支,不能代替完整图像解码测试。
这里还有一个业务上的误区:为了消除“宽高过小”告警而直接放大图片,不会凭空获得原图没有记录的商品细节。程序负责发现配置差距,是否补拍、重新导出或者调整版位,应由素材用途决定。文件指标与商品信息质量要分开看。
上线前再给文件记录加上 SKU、用途和版本,预检结果才方便回到具体图片。这个小脚本适合承担导出后的第一道筛选,不适合单独承担发布审核。
参考资料
[1] Picset 产品介绍(国际站提示升级为 Picell AI;本文不涉及其 API。)