Is it possible somehow to join up PNG images to an APNG animated image using nodejs?
I've found PHP library only: link
Is it possible somehow to join up PNG images to an APNG animated image using nodejs?
I've found PHP library only: link
UPNG.js can parse and build APNG files - https://github.com/photopea/UPNG.js
From the readme -
UPNG.js supports APNG and the interface expects "frames".
UPNG.encode(imgs, w, h, cnum, [dels])
imgs: array of frames. A frame is an ArrayBuffer containing the pixel data (RGBA, 8 bits per channel) w, h : width and height of the image cnum: number of colors in the result; 0: all colors (lossless PNG) dels: array of delays for each frame (only when 2 or more frames) returns an ArrayBuffer with binary data of a PNG file
UPNG.js can do a lossy minification of PNG files, similar to TinyPNG and other tools. It performs color quantization using the k-means algorithm.
Lossy compression is allowed by the last parameter cnum. Set it to zero for a lossless compression, or write the number of allowed colors in the image. Smaller values produce smaller files. Or just use 0 for lossless / 256 for lossy.
UPNG.encode(imgs, w, h, cnum, [dels])
how do you take the object returned and turn it into a URL to download the actual apng file? –
Help There is no library for that, but it is quite simple to implement. Algorithm for merging multiple PNG files into single APNG is described in Wikipedia:
- Take all chunks of the first PNG file as a building basis.
- Insert an animation control chunk (acTL) after the image header chunk (IHDR).
- If the first PNG is to be part of the animation, insert a frame control chunk (fcTL) before the image data chunk (IDAT).
- For each of the remaining frames, add a frame control chunk (fcTL) and a frame data chunk (fdAT). Then add the image end chunk (IEND). The content for the frame data chunks (fdAT) is taken from the image data chunks (IDAT) of their respective source images.
Here is an example implementation:
const fs = require('fs')
const crc32 = require('crc').crc32
function findChunk(buffer, type) {
let offset = 8
while (offset < buffer.length) {
let chunkLength = buffer.readUInt32BE(offset)
let chunkType = buffer.slice(offset + 4, offset + 8).toString('ascii')
if (chunkType === type) {
return buffer.slice(offset, offset + chunkLength + 12)
}
offset += 4 + 4 + chunkLength + 4
}
throw new Error(`Chunk "${type}" not found`)
}
const images = process.argv.slice(2).map(path => fs.readFileSync(path))
const actl = Buffer.alloc(20)
actl.writeUInt32BE(8, 0) // length of chunk
actl.write('acTL', 4) // type of chunk
actl.writeUInt32BE(images.length, 8) // number of frames
actl.writeUInt32BE(0, 12) // number of times to loop (0 - infinite)
actl.writeUInt32BE(crc32(actl.slice(4, 16)), 16) // crc
const frames = images.map((data, idx) => {
const ihdr = findChunk(data, 'IHDR')
const fctl = Buffer.alloc(38)
fctl.writeUInt32BE(26, 0) // length of chunk
fctl.write('fcTL', 4) // type of chunk
fctl.writeUInt32BE(idx ? idx * 2 - 1 : 0, 8) // sequence number
fctl.writeUInt32BE(ihdr.readUInt32BE(8), 12) // width
fctl.writeUInt32BE(ihdr.readUInt32BE(12), 16) // height
fctl.writeUInt32BE(0, 20) // x offset
fctl.writeUInt32BE(0, 24) // y offset
fctl.writeUInt16BE(1, 28) // frame delay - fraction numerator
fctl.writeUInt16BE(1, 30) // frame delay - fraction denominator
fctl.writeUInt8(0, 32) // dispose mode
fctl.writeUInt8(0, 33) // blend mode
fctl.writeUInt32BE(crc32(fctl.slice(4, 34)), 34) // crc
const idat = findChunk(data, 'IDAT')
// All IDAT chunks except first one are converted to fdAT chunks
let fdat;
if (idx === 0) {
fdat = idat
} else {
const length = idat.length + 4
fdat = Buffer.alloc(length)
fdat.writeUInt32BE(length - 12, 0) // length of chunk
fdat.write('fdAT', 4) // type of chunk
fdat.writeUInt32BE(idx * 2, 8) // sequence number
idat.copy(fdat, 12, 8) // image data
fdat.writeUInt32BE(crc32(4, length - 4), length - 4) // crc
}
return Buffer.concat([ fctl, fdat ])
})
const signature = Buffer.from('\211PNG\r\n\032\n', 'ascii')
const ihdr = findChunk(images[0], 'IHDR')
const iend = Buffer.from('0000000049454e44ae426082', 'hex')
const output = Buffer.concat([ signature, ihdr, actl, ...frames, iend ])
fs.writeFileSync('output.png', output)
fdat
should say fdat.writeUInt32BE(crc32(fdat.slice(4, length - 4), length - 4))
. –
Palp Currently, no it doesn't look like it. Wikipedia lists the available software, and as you can see there's no support for ImageMagick which has a Node wrapper. However, you may find you can download the command line tool apngasm and shell out to it, if you find it's worth your while there are Node command line wrappers to hook this into an existing application using child_process
(http://nodejs.org/api/child_process.html).
I'm not sure about nodejs, but you could try APNG-canvas. APNG uses HTML5 (-webkit-canvas), JavaScript (jQuery).
"APNG-canvas is a library for displaing Animated PNG files in the browsers with canvas support (Google Chrome, Internet Explorer 9, Apple Safari)."
Working demo is here.
© 2022 - 2024 — McMap. All rights reserved.