Picset/文章中心/AI玩法
AI玩法

用 Node.js 给 PNG 商品图做一道文件检查

封面由 AI 生成,仅作主题示意。

Picset 内容研究组 · 发布/核验于 2026年9月29日 · 约 3 分钟阅读
用 Node.js 给 PNG 商品图做一道文件检查
简要回答

封面由 AI 生成,仅作主题示意。

封面:PNG 商品图预检

封面由 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。)

[2] W3C:PNG Specification,文件结构与 IHDR