在 NodeJS 中创建 PNG IDAT 块

问题描述 投票:0回答:2

我正在尝试使用 Node.js 手动创建 16 位灰度 PNG,但 IDAT 块既“有效”(在某些查看器中),但也不起作用,因为数据在查看器中看起来不正确。加载,并且 Photoshop 不喜欢 if 因为“自适应滤镜值不好”。 关于如何指示“无过滤”的规范并不是非常清楚,因此我在

IHDR
块中将过滤器方法设置为 0,但我无法弄清楚
IDAT
中的位置来指示没有进行过滤。

我当前使用的代码是:

import { writeFileSync } from "fs";
import zlib from "zlib";
import crc32 from 'crc/crc32'; // the node-crc library

// string to bytes
function toBytes(v) {
  return v.split(``).map((v) => v.charCodeAt(0));
}

// encode a number as four-byte value
function fourByte(n) {
  return [
      (n & 0xff000000) >> 24,
      (n & 0xff0000) >> 16,
      (n & 0xff00) >> 8,
      n & 0xff,
    ]; 
  };
}

// create a PNG chunk
function makeChunk(type, data = []) {
  const typeBytes = toBytes(type);
  const length = fourByte(data.length);
  const buffer = [...typeBytes, ...data];
  const crc = crc32(buffer);
  return Buffer.from([...length, ...buffer, ...fourByte(crc)]);
}

// create a sample 16 bit pixel raster
const [w, h] = [10, 10];
const pngPixels = new Uint16Array(w * h);
for (let x = 0; x < w; x++) {
  for (let y = 0; y < h; y++) {
    const i = x + y * w;
    pngPixels[i] = i * 100;
  }
}

// PNG preamble
const magic = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);

// header for 16 bit greyscale image
const IHDR = makeChunk(`IHDR`, [
  ...fourByte(w), // width
  ...fourByte(h), // height
  16, // bit depth: 16 bits
  0, // color: greyscale
  0, // compression method, must be 0
  0, // filter method, must be 0
  0, // interlace method: no interlacing
]);

// deflate the pixels and form data chunk
const IDAT = makeChunk(`IDAT`, zlib.deflateSync(pngPixels.buffer, { level: 9 }));

// turn into a file and write to disk.
const pngData = Buffer.concat([magic, IHDR, IDAT, makeChunk(`IEND`)]);
writeFileSync(`test-out.png`, pngData);

我显然没有存储过滤器方法 0 也应该使用过滤器 type 0(“无”过滤)这一事实,但我一生都无法弄清楚该信息的去向。有谁知道我错过了什么/在哪里以及如何设置?

javascript png
2个回答
1
投票

官方规范中的信息似乎奇怪地缺失(或只是隐藏得很好),但https://www.w3.org/TR/PNG-DataRep.html#DR.Filtering上的古老规范文档解释说过滤器字节必须位于每个像素栅格的扫描线(即每行像素)之前,仅使用颜色深度的一个字节不考虑(因此16位颜色中的10个像素,包含20个字节,成为21字节扫描线,而不是22 字节扫描线)。

在这种情况下,这意味着更新后的代码如下所示:

...

// 16 bit PNG image data must use big endian ordering,
// so we better find out whether we're going to need to
// do some byte reordering later on:
const LITTLE_ENDIAN = Symbol(`little endian`);
const BIG_ENDIAN = Symbol(`big endian`);
const endian = (function checkEndian() {
  const buf = new ArrayBuffer(2);
  const u8 = new Uint8Array(buf);
  const u16 = new Uint16Array(buf);
  u8.set([0xaa, 0xbb], 0);
  return u16[0] === 0xbbaa ? LITTLE_ENDIAN : BIG_ENDIAN;
})();

...

// Create our sample 16 bit pixel raster again
const [w, h] = [10, 10];
const pngPixels = new Uint16Array(w * h);
for (let x = 0; x < w; x++) {
  for (let y = 0; y < h; y++) {
    const i = x + y * w;
    pngPixels[i] = ((i / 99) * (2 ** 16 - 1)) | 0;
  }
}

// Convert the pixel raster to scan lines that start with
// a zero byte to indicate filter type "none".

// First, convert our 16 bit values to 8 bit values:
const pngPixels8 = new Uint8Array(pngPixels.buffer);

// Then check if we need to do some endian byte swapping:
if (endian === LITTLE_ENDIAN) {
  for (let i = 0, e = pngPixels8.length; i < e; i += 2) {
    let _ = pngPixels8[i];
    pngPixels8[i] = pngPixels8[i + 1];
    pngPixels8[i + 1] = _;
  }
}

// Then create our new filter-byte-padded scanline array:
const filtered = new Uint8Array(pngPixels8.length + h);
for (let y = 0; y < h; y++) {
  const s = 2 * y * w;
  const d = 1 + y + s;
  filtered.set(pngPixels8.subarray(s, s + 2 * w), d);
}

...

const IDAT = makeChunk(`IDAT`, zlib.deflateSync(filtered, { level: 9 }));

...

(当然,你也可以使用DataView进行数据类型和字节序转换,尽管在这种情况下,因为我们需要在很多地方注入0x00,所以哑转换就可以了)


0
投票

��� IHDR��������ØÔÌ���'PLTELiq��������������������������������� �´×œv��� tRNS�¿?ì WÖjš

m��� pHYs�����šœ��TIDATxÚíÛv‚0E™p×üÿ÷v¡Xµ $a&FºÏCWZfMv2sQª !„þ¯D„Þiƒrt:1åÈ9Æ�Ð@Ѐ‚44     h h@AÐ@Ѐ‚‚44   eùÈÎ÷‘‰†uòûãA,¯>}~ÃvVIUgëTr9†oœ÷®±LâhAœÙÅ»Þû¶õ¾¬²˜ƒôµ÷}—#ˆy&U<·Þ5Î ÎûZ,²˜.Z·—){œ F«îäÛñÚ7æ_Å,ˆˆe1G£+_/>ôÞ×Óª;Ÿ+ùå.ÊAîsõar)ir1]uÝs‰}¨‰òAº¼™h/íÖŸnóGä>ÇZ-üÓ5Æ—I:Ñ2¢¿­ï]ÛL´¶‹#2CR)[A­ ÷†‘; kùRt½wƒRz!ˆÎ²¿ÕòeRUU·kVm5¹àAV†B¥%nAÍ‘‰TýžþôàfxWÚ ²Ë.ε|µàåÈdÍÄ�asrµi—Ùí —5:ì ²192™–NJ|õ‚†.Ü^î0¢{¥Ogrû«È�"1ó$±²¿7œëAÚ„  Ú<“çͧæÒÞYÙ£ÿ'ÁíJôÿXg"×fn§SüEô-†”YmDS6væ™Ü²há…ÿqjeO­Ðá=
G´1ÏdÎ"�]�K�gb\WJ� QS0w&Á.´Ù±Ó C¹ÏÙvÌ€½Ò'3™³Xkhá®vGeß¿ ¡$÷Y%¥f²Qââ g´HíJQ•Dç.¹y&kíÎpÜ} dmùªÝ-]q®:³K&Kå4Ê�„ÝzŽ])�',Þ7/1“·vZíŽòZMÔ~Êö¶'h?5ÏäõZšt–FE�~ W⁄g æ™<×Cå'‘ï7šõc©EX�Ï’ÉÓjÐ~Jÿ§&ëÏÝtI÷¸R¸.“ªSLkÚb:ý¸¶g(憪fC>ÉÍE™Áªnς+w¥· ]Ó†,fâÌ2¹V@£3XÏCÕšžŸ7b'Ó#Îs&Þ, 'ªñµý¡ùnììс £X 'ªªN-¤Y Ÿ(1ÿQ– •xg™B 9†)SCÐ8Œ h@€4 h@€4 h@€4 h@Ð@Ѐ‚4 h@‡¤ájhü ÔЈ1kAЀ‚4 4 h h@A=Ó�G1jsŽÛÅíðu·Ë
€·[ªÛEŸݶfq”³9?ùžbUŽªn Q„Bè_é80TLúÚ����IEND®B

© www.soinside.com 2019 - 2024. All rights reserved.