I have a compact canvas-to-png download saver function (see code below).
This code works very well and I am satisfied with its output... mostly.
Would a second replace suffice? What would that replace look like?
My only other option is to post-process the file with imagemagick.
Any ideas?
More completely: I want to add metadata from javascript.
I found this link http://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_PNG_files
which details the structures, and I may be able to figure it out with sufficient time.
If anyone has experience and can shorten this for me, I would appreciate it.
//------------------------------------------------------------------
function save () // has to be function not var for onclick to work.
//------------------------------------------------------------------
{
var element = document.getElementById("saver");
element.download = savename;
element.href = document.
getElementById(id.figure1a.canvas).
toDataURL("image/png").
replace(/^data:image\/[^;]/,'data:application/octet-stream');
}
The Base-64 representation has little to do with the internal chunks. It's just [any] binary data encoded as string so it can be transferred over string-only protocols (or displayed in a textual context).
It's perhaps a bit broad to create an example, but hopefully showing the main steps will help to achieve what you're looking for:
- To add a chunk to a PNG you would first have to convert the data for it into an ArrayBuffer using XHR/fetch in the case of Data-URIs, or FileReader in case you have the PNG as Blob (which I recommend. See
toBlob()
).
- Add a DataView to the ArrayBuffer
- Go to position 0x08 in the array which will represent the start of the IHDR chunk, read the length of the chunk (Uint32) (it's very likely it has the same static size for almost any PNG but since it's possible to have changes, and you don't need to remember the chunk size we'll just read it from here). Add length to position (+4 for CRC-32 at the end of the chunk, and +4 if you didn't move the pointer while reading the length), typically this should land you at position 0x21.
- You now have the position for the next chunk which we can use to insert our own text chunks
- Split that first part into a part-array (a regular array) using a sub-array with the original ArrayBuffer, e.g.
new Uint8Array(arraybuffer, 0, position);
- you can also use the subarray method.
- Produce the new chunk* as typed array and add to part-array
- Add the remaining part of the original PNG array without the first part to the part-array, e.g.
new Uint8Array(arraybuffer, position, length - position);
- Convert the part-array to a Blob using the part-array directly as argument (
var newPng = new Blob(partArray, {type: "image/png"})
;). This will now contain the custom chunk. From there you can use an Object-URL with it to read it back as an image (or make it available for download).
*) Chunk:
- For
tEXt
be aware of it is limited to the Latin-1 charset which means you'll have to whitewash the string you want to use - use iTXt
for unicode (UTF-8) content - we'll use tEXt
here for simplicity.
- The keyword and value is separated by a NUL-byte (0x00) in a
tEXt
chunk, and the keyword must be exactly typed as defined in the spec.
- Build the chunk this way:
- get byte-size from string
- add 12 bytes (for length, four-cc and crc-32)
- format the array this way (you can use a DataView here as well):
Uint32 - length of chunk (data only in number of bytes)
Uint32 - "tEXt" as four-cc
[...] - The data itself (copy byte-wise)
Uint32 - CRC32* which includes the FourCC but not length and itself.
All data in a PNG is big-endian.
To calculate CRC-32 feel free to use this part of my pngtoy solution (the LUT is built this way). Here is one way to format a four-cc:
function makeFourCC(n) { // n = "tEXt" etc., big-endian
var c = n.charCodeAt.bind(n);
return (c(0) & 0x7f) << 24 | (c(1) & 0x7f) << 16 | (c(2) & 0x7f) << 8 | c(3) & 0x7f
}