DEV Community

Alexey Boyko
Alexey Boyko

Posted on • Edited on

JavaScript diagram editor that renders diagrams from PNG images (open source)

Fig 1. dgrm.net can open diagrams from PNG images
Fig 1. dgrm.net can open diagrams from PNG images

dgrm.net | GitHub

<< Previous article

dgrm.net - is a diagram editor, with an eye to transformation into a knowledge map tool.

Distinctive features:

  • asceticism,
  • works on phones,
  • open source.

In the development process, interesting moments appear. Today we will talk about reading data from PNG. The source code for use in your projects is attached.

Why open diagrams from PNG images?

Developer-made user interfaces are notorious for being weird. Perhaps the idea of using images as project files is just that. At least the approach is original.

All editors use their own project files. But this is inconvenient:

  • no previews,
  • when sending an image, you must also send the source.

It is more convenient to have a picture of the diagram, which can be edited if necessary.

Looking at Figure 1, we can assume that steganography, or image recognition, is being used. It's actually much simpler, and without hacks - the PNG format supports storing additional information, such as a timestamp, author's name, or any other.

dgrm.net writes JSON with diagram data to png files.

PNG Chunks

Here is the PNG specification: “Portable Network Graphics (PNG) Specification”.

Highlight:

  • png files are made up of blocks called chunks,
  • you can add your own chunks to the file.

Fig 2. Structure of one PNG chunk
Fig 2. Structure of one PNG chunk

For custom data you can think of any chunk name (for example “dgRm”):

  • The length of the name is strictly 4 Latin letters;
  • Letter case matters. For custom chunks, put all letters in lowercase, and the 3rd in uppercase.

Thus, to store a JSON string inside a PNG file, you need to add your own chunk to the file.

Read/Write PNG chunks in JavaScript in the browser

Read a chunk

Chunks follow each other, the required chunk is searched by enumeration.

Chunk search algorithm (listing 1):

  1. take the name of the first chunk
  2. if the name does not match the search one
    • take the length of the chunk (the first 4 bytes see Fig. 2)
    • knowing the length of the chunk, move the cursor to the beginning of the next chunk
  3. repeat 1 and 2 until we find the desired chunk or 'IEND' (end of file).
/**
 * @param {ArrayBuffer} pngData
 * @param {number} chunkNameUint32 chunk name as Uint32
 * @returns {DataView | null} chunk data
 */
function chunkGet(pngData, chunkNameUint32) {
    const dataView = new DataView(pngData, 8); // 8 byte - png signature

    let chunkPosition = 0;
    let chunkUint = dataView.getUint32(4);
    let chunkLenght;
    while (chunkUint !== 1229278788) { // last chunk 'IEND'
        chunkLenght = dataView.getUint32(chunkPosition);
        if (chunkUint === chunkNameUint32) {
            return new DataView(pngData, chunkPosition + 16, chunkLenght);
        }
        chunkPosition = chunkPosition + 12 + chunkLenght;
        chunkUint = dataView.getUint32(chunkPosition + 4);
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

Listing 1. Chunk lookup function

Quick reference:
JavaScript has an interesting way of working with binary data.

Quote:
The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer. …
You cannot directly manipulate the contents of an ArrayBuffer
developer.mozilla.org

To read the data, you can wrap it in a DataView. The DataView allows you to read the data in any position as a number (using the getInt8(), getUint32() methods, etc.).

Write a chunk

To write a chunk, you need to insert a new chunk into the chain. If a chunk with the given name already exists, it must be replaced.

See implementation on GitHub - the chunkSet function.

Source code

Functions for working with PNG chunks are located in one file. The file has no dependencies, so you can simply copy it into your project.

png-chunk-utils.js

Usage example:

// Write a chunk, new blob output
const newPngBlob = await pngChunkSet(
    // png-image
    pngBlob,
    // chunk name
    'dgRm',
    // chunk value: string as a bytes
    new TextEncoder().encode('...'));


// read a chuk
const dgrmChunkVal = await pngChunkGet(newPngBlob, 'dgRm');
const str = new TextDecoder().decode(dgrmChunkVal);
Enter fullscreen mode Exit fullscreen mode

Listing 2. Calling functions to write and read PNG chunks

Other articles about dgrm.net

How to support the project

  • Start using, tell us what you think. Any way: comments, private messages, on GitHub. I read everything, I keep a list of proposals.
  • Tell your friends.
  • Give a start on GitHub.

Top comments (0)