Онлайн доска DGRM.net хранит данные в PNG-картинках. Вместе с вложениями файлы получаются большие. Рассказываю как сделано хранение данных в PNG-файлах.

Рис 1. Онлайн доска DGRM.net открывает схемы из PNG картинок
Рис 1. Онлайн доска DGRM.net открывает схемы из PNG картинок

Формат PNG-файла

Файл PNG состоит из блоков. Блоки содержат разную информацию. Например блок tIME содержит дату редактирования.

В конце идет обязательный блок IEND. После IEND можно дописать в файл свои данные и картинка не сломается. Это использует DGRM: пишет свои данные в конец PNG файла.

Получается такой файл - рис 2.

Рис 2. Файл картинки с данными DGRM
Рис 2. Файл картинки с данными DGRM

Структура PNG блока - рис 3.

Рис 3. Структура PNG блока
Рис 3. Структура PNG блока

Хранение в DGRM Data организовано также блоками, только немного другого формата. Первый блок - JSON-фигур, потом вложения.

Чтение из файла без загрузки всего файла в память

Получить ссылку на файл на устройстве пользователя можно с помощью HTMLInputElement. При этом данные файла не будут загружены в память - листинг 1.

/**
 * @param {string} accept
 * @param {FileCallback} callBack
 * @param {(evt:Event)=>void} cancelCallBack
 */
const fileInputOpen = (accept, callBack, cancelCallBack) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.multiple = false;
    input.accept = accept;
    input.style.display = 'none';
    document.body.appendChild(input);

    const dispose = () => input?.remove();

    input.oncancel = evt => {
        cancelCallBack(evt);
        dispose();
    };
    input.onchange = () => {
        callBack((!input.files?.length) ? null : input.files[0]);
        dispose();
    };

    input.click();
}

Листинг 1. Получение ссылки на файл

При открытии файла нужно найти где начинаются DGRM данные, т.е. блок IEND.

Для поиска нужно перебрать блоки от начала файла до искомого блока. При этом не желательно грузить весь файл в память.

Функция pngChunkDataPositionGet последовательно читает только по 8 байт (длина + заголовок) и проматывает до следующего блока пока не найдет нужный - листинг 2.

// IEND
const PNG_CHUNK_END_NAME_UINT32 = 1229278788;


/**
 * @param {Blob} pngFile, @param {number} chankNameUint32
 * @returns {Promise<[startBytePosition:number, endBytePosition:number]>}
 */
const pngChunkDataPositionGet = async (pngFile, chankNameUint32) => {
    /** @param {number} pos */
    const uint32Get =
        async pos => uint32From4BytesBlob(pngFile.slice(pos, pos + 4));

    /** @type {number} */ let chunkPosition = 8; // 8 byte - png signature
    /** @type {number} */ let chunkLenght;
    /** @type {number} */ let chunkName;
    /** @type {number} */ let chunkDataStart;
    /** @type {number} */ let chunkDataEnd;

    do {
        chunkLenght = await uint32Get(chunkPosition);
        chunkName = await uint32Get(chunkPosition + 4);
        chunkDataStart = chunkPosition + 8;
        chunkDataEnd = chunkDataStart + chunkLenght;

        if (chunkName === chankNameUint32) {
            return [chunkDataStart, chunkDataEnd];
        }

        chunkPosition = chunkDataEnd + 4;
    } while (chunkName !== PNG_CHUNK_END_NAME_UINT32);

    // looking for end chunk
    if (chunkName === chankNameUint32) {
        return [chunkDataStart, chunkDataEnd];
    }

    return null;
};

Листинг 2. Поиск блока в PNG файле

Мотать до IEND большие PNG не быстро. Поэтому имеет смысл добавить свой блок в начало файла. В этом блоке указать кол-во байт до конца DGRM Data, т.е. размер PNG картинки без дополнительной DGRM Data.

Рис 4. Свой блок dgRp в начале PNG-файла. Указывает на начало DGRM data.
Рис 4. Свой блок dgRp в начале PNG-файла. Указывает на начало DGRM data.

В DGRM Data первый блок это JSON-фигур, потом идут блоки вложений - рис 5.

Рис 5. Блоки DGRM Data
Рис 5. Блоки DGRM Data

Блок JSON грузится в память целиком. Вложения грузятся только если их нет в кэше.
Вложения могут быть большими, поэтому их тоже не желательно грузить целиком в память. Подробнее во второй части статьи.

Во второй части статьи про формирование больших файлов в браузере.

Комментарии (0)