You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

生成带真实物理尺寸的PNG以实现打印机自动识别打印尺寸的方法

解决方案

核心结论

  • 添加PNG标准定义的pHYs chunk是实现打印自动识别物理尺寸的通用方案,主流操作系统、打印软件、办公软件均支持读取该字段获取DPI信息,无需用户手动调整打印尺寸
  • 你当前代码的像素计算逻辑是正确的(英寸数值*DPI得到像素宽高),只需要在导出PNG时手动插入pHYs chunk即可

实现原理

PNG的pHYs chunk存储的是图片的物理像素密度,单位为像素每米,300DPI换算为像素每米的公式为:300 / 0.0254 ≈ 11811,把这个值写入pHYs chunk后,打印程序就会自动用像素宽高除以DPI得到实际打印尺寸,和你预设的2×1英寸完全匹配。

修改后的完整代码

你只需要替换原有JS中canvas.toBlob的部分逻辑,给导出的PNG插入pHYs chunk即可:

JS代码

const canvas = document.getElementById("preview"),
      ctx = canvas.getContext("2d"),
      width = document.getElementById("width"),
      height = document.getElementById("height"),
      link = document.getElementById("download"),
      dpi = 300,
      offset = 0.5; //make lines true 1px wide by shifting it 0.5 pixels

// 插入pHYs chunk的工具函数
function insertPngPhysChunk(blob, dpi) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = () => {
      const arr = new Uint8Array(reader.result);
      // PNG文件头是8字节,之后是IHDR chunk
      const physChunk = new Uint8Array(21); // 4长度 + 4标识 + 9数据 + 4校验和
      // 写入chunk长度:9字节数据
      physChunk[0] = 0; physChunk[1] = 0; physChunk[2] = 0; physChunk[3] = 9;
      // 写入pHYs标识
      physChunk.set([0x70, 0x48, 0x59, 0x73], 4);
      // 写入X方向像素每米
      const pxPerM = Math.round(dpi / 0.0254);
      physChunk[8] = (pxPerM >>> 24) & 0xff;
      physChunk[9] = (pxPerM >>> 16) & 0xff;
      physChunk[10] = (pxPerM >>> 8) & 0xff;
      physChunk[11] = pxPerM & 0xff;
      // 写入Y方向像素每米和X一致
      physChunk.set(physChunk.slice(8, 12), 12);
      // 写入单位标识:1表示单位为米
      physChunk[16] = 1;
      // 计算CRC校验和
      let crc = 0;
      for (let i = 4; i < 17; i++) {
        crc ^= physChunk[i];
        for (let j = 0; j < 8; j++) {
          crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
        }
      }
      // 写入CRC
      physChunk[17] = (crc >>> 24) & 0xff;
      physChunk[18] = (crc >>> 16) & 0xff;
      physChunk[19] = (crc >>> 8) & 0xff;
      physChunk[20] = crc & 0xff;
      // 把pHYs chunk插入到IHDR chunk之后,IDAT chunk之前
      const newArr = new Uint8Array(arr.length + physChunk.length);
      // 先写8字节PNG头 + 25字节IHDR chunk
      newArr.set(arr.slice(0, 33), 0);
      // 插入pHYs chunk
      newArr.set(physChunk, 33);
      // 写入剩余的原有内容
      newArr.set(arr.slice(33), 33 + physChunk.length);
      resolve(new Blob([newArr], { type: 'image/png' }));
    };
    reader.readAsArrayBuffer(blob);
  });
}

function generate()
{
  const w = width.value * dpi,
        h = height.value * dpi,
        text = width.value + "x" + height.value;

  canvas.width = w + offset * 2;
  canvas.height = h + offset * 2;
  ctx.strokeStyle = "black";
  ctx.fillStyle = "white";
  ctx.lineWidth = 1;
  ctx.fillRect(0, 0, canvas.width, canvas.height); //fill background
  ctx.rect(offset, offset, w, h);
  // diagonal lines
  ctx.moveTo(offset, offset);
  ctx.lineTo(w, h);
  ctx.moveTo(w + offset, offset);
  ctx.lineTo(offset, h);
  // vertical line
  ctx.moveTo(w / 2 + offset, offset + offset);
  ctx.lineTo(w / 2, h);
  // horizontal line
  ctx.moveTo(offset, h / 2 + offset);
  ctx.lineTo(w, h / 2);
  // circle
  ctx.moveTo(w / 2 + Math.min(w, h) / 2 + offset, h / 2 + offset);
  ctx.arc(w / 2 + offset, h / 2 + offset, Math.min(w, h) / 2, 0, Math.PI * 2);
  // text
  ctx.font = Math.min(w, h) / (text.length / 1.5) + "px monospace";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.strokeText(text, w / 2 + offset, h / 2 + offset);
  ctx.stroke();
  //generate download link
  canvas.toBlob(async data => {
    // 插入pHYs chunk
    const pngWithPhys = await insertPngPhysChunk(data, dpi);
    link.setAttribute("download", `${text}_(${dpi}dpi).png`);
    link.href = URL.createObjectURL(pngWithPhys);
  });
}

generate();

CSS代码(无修改)

input
{
  width: 3em;
}
div
{
  padding: 1em;
  background-color: pink;
  display: inline-block;
}

div:first-of-type
{
  padding-bottom: 0;
}

HTML代码(无修改)

<div>
  Size (in inches): <input id="width" type="number" oninput="generate()" value="2"> x <input id="height" type="number" oninput="generate()" value="1">
</div>

<a id="download">download PNG</a>
<br>
<div>
  <canvas id="preview"></canvas>
</div>

注意事项

  • 打印时请确认打印软件的「缩放」选项设置为「实际大小」/「100%」,部分软件默认会开启「适应纸张」缩放,会覆盖pHYs chunk的尺寸设置
  • 极个别老旧打印软件不支持读取pHYs chunk,这种场景才需要手动设置打印尺寸,主流场景下均可以自动识别

内容的提问来源于stack exchange,提问作者vanowm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 12:27:03