Source: encode.js

  1. import crc32 from "./crc32.js";
  2. import { ChunkType, ColorType, PNG_HEADER } from "./constants.js";
  3. import { encode_IHDR, encode_IDAT_raw } from "./chunks.js";
  4. /**
  5. * @typedef {Object} EncodeOptions
  6. * @property {Uint8Array} data the raw pixel data to encode
  7. * @property {number} width the width of the image
  8. * @property {number} height the height of the image
  9. * @property {ColorType} [colorType=ColorType.RGBA] the color type of the pixel data
  10. * @property {number} [depth=8] the bit depth of the image
  11. * @property {number} [filterMethod=FilterMethod.Paeth] the filter method to use
  12. * @property {number} [firstFilter=filter] the first scanline filter method to use
  13. * @property {Chunk[]} [ancillary=[]] additional chunks to include in the PNG
  14. */
  15. /**
  16. * Encodes a PNG buffer from the given image and options, using the specified `deflate` algorithm and optional compression options.
  17. * The deflate function should have the signature `(buf, [deflateOptions]) => Uint8Array`.
  18. *
  19. * @param {EncodeOptions} options the encoding options
  20. * @param {Function} deflate the sync deflate function to use
  21. * @param {Object} [deflateOptions] optional deflate options passed to the deflate() function
  22. */
  23. export function encode(options = {}, deflate, deflateOptions) {
  24. const { data, ancillary = [], colorType = ColorType.RGBA } = options;
  25. if (!data) throw new Error(`must specify { data }`);
  26. if (!deflate) throw new Error(`must specify a deflate function`);
  27. if (colorType !== ColorType.RGB && colorType !== ColorType.RGBA) {
  28. throw new Error(
  29. "only RGB or RGBA colorType encoding is currently supported"
  30. );
  31. }
  32. return writeChunks([
  33. { type: ChunkType.IHDR, data: encode_IHDR(options) },
  34. ...ancillary,
  35. {
  36. type: ChunkType.IDAT,
  37. data: deflate(encode_IDAT_raw(data, options), deflateOptions),
  38. },
  39. { type: ChunkType.IEND },
  40. ]);
  41. }
  42. /**
  43. * Encodes just the raw PNG header into a Uint8Array buffer.
  44. * @returns {Uint8Array} the PNG header
  45. */
  46. export function encodeHeader() {
  47. return PNG_HEADER.slice();
  48. }
  49. /**
  50. * Encodes a single PNG chunk into a Uint8Array buffer, by writing the chunk length, type, data, and CRC value.
  51. * @param {Chunk} chunk the chunk to encode
  52. * @returns {Uint8Array} the encoded chunk buffer
  53. */
  54. export function encodeChunk(chunk) {
  55. const length = chunk.data ? chunk.data.length : 0;
  56. const output = new Uint8Array(4 + length + 4 + 4);
  57. const dv = new DataView(output.buffer, output.byteOffset, output.byteLength);
  58. // Write chunk length
  59. let idx = 0;
  60. encodeChunkRaw(output, dv, chunk, idx);
  61. return output;
  62. }
  63. /**
  64. * Writes and formats an array of PNG chunks into a complete PNG buffer, including the PNG header.
  65. *
  66. * @param {Chunk[]} chunks the array of chunks to encode
  67. * @returns {Uint8Array} the encoded PNG buffer
  68. */
  69. export function writeChunks(chunks) {
  70. let totalSize = PNG_HEADER.length; // start with header
  71. let idx = totalSize;
  72. for (let chunk of chunks) {
  73. totalSize += chunk.data ? chunk.data.length : 0;
  74. totalSize += 12; // length, code, CRC value (4 bytes each)
  75. }
  76. const output = new Uint8Array(totalSize);
  77. const dv = new DataView(output.buffer);
  78. // write header
  79. output.set(PNG_HEADER, 0);
  80. for (let chunk of chunks) {
  81. idx = encodeChunkRaw(output, dv, chunk, idx);
  82. }
  83. return output;
  84. }
  85. function encodeChunkRaw(output, dv, chunk, idx = 0) {
  86. // Write chunk length
  87. const length = chunk.data ? chunk.data.length : 0;
  88. dv.setUint32(idx, length);
  89. idx += 4;
  90. // Where the chunk index starts (before type code)
  91. const chunkStartIdx = idx;
  92. const chunkDataStartIdx = idx + 4;
  93. const chunkDataEndIdx = chunkDataStartIdx + length;
  94. // Write chunk type code
  95. const type = chunk.type;
  96. dv.setUint32(chunkStartIdx, type);
  97. // Write chunk data
  98. if (chunk.data) output.set(chunk.data, chunkDataStartIdx);
  99. // get the whole chunk buffer including type
  100. const chunkBuf = output.subarray(chunkStartIdx, chunkDataEndIdx);
  101. // compute CRC and write it
  102. const crcValue = crc32(chunkBuf);
  103. dv.setInt32(chunkDataEndIdx, crcValue);
  104. // return next index for reading
  105. return chunkDataEndIdx + 4;
  106. }