dgrm.net | Гитхаб

‹‹ Предыдущая статья

dgrm.net — редактор диаграмм с прицелом на преобразование в инструмент карты знаний.

Отличительные черты:

  • аскетизм,
  • работает на телефонах,
  • Открытый исходный код.

В процессе разработки появляются интересные моменты. Сегодня мы поговорим о чтении данных из PNG. Исходный код для использования в ваших проектах прилагается.

Зачем открывать диаграммы из изображений PNG?

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

Все редакторы используют свои собственные файлы проектов. Но это неудобно:

  • нет превью,
  • при отправке изображения необходимо также прислать исходник.

Удобнее иметь картинку схемы, которую при необходимости можно отредактировать.

Глядя на рисунок 1, мы можем предположить, что используется стеганография или распознавание изображений. На самом деле все намного проще и без хаков — формат PNG поддерживает сохранение дополнительной информации, например, метки времени, имени автора и любой другой.

dgrm.net записывает JSON с данными диаграммы в файлы png.

PNG фрагменты

Вот спецификация PNG: «Спецификация Portable Network Graphics (PNG)».

Выделять:

  • png файлы состоят из блоков, называемых чанками,
  • вы можете добавить свои собственные фрагменты в файл.

Для пользовательских данных вы можете придумать любое имя чанка (например, «dgRm»):

  • Длина имени строго 4 латинские буквы;
  • Регистр букв имеет значение. Для пользовательских чанков поставьте все буквы в нижнем регистре, а 3-ю в верхнем регистре.

Таким образом, чтобы сохранить строку JSON внутри файла PNG, вам нужно добавить в файл свой собственный фрагмент.

Чтение/запись фрагментов PNG в JavaScript в браузере

Прочитать фрагмент

Чанки следуют друг за другом, нужный чанк ищется перебором.

Алгоритм поиска фрагментов (листинг 1):

  1. взять имя первого чанка
  2. если имя не совпадает с искомым
    - взять длину чанка (первые 4 байта см. рис. 2)
    - зная длину чанка, переместить курсор в начало чанка следующий кусок
  3. повторяем 1 и 2, пока не найдем нужный фрагмент или ‘IEND’ (конец файла).
/**
 * @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;
}

Листинг 1. Функция поиска фрагментов

Краткий справочник:
JavaScript имеет интересный способ работы с двоичными данными.

Цитата:
Объект ArrayBuffer используется для представления универсального буфера необработанных двоичных данных фиксированной длины. …
Вы не можете напрямую манипулировать содержимым ArrayBuffer
developer.mozilla.org

Чтобы прочитать данные, вы можете обернуть их в DataView. DataView позволяет читать данные в любой позиции в виде числа (используя методы getInt8(), getUint32() и т. д.).

Написать кусок

Чтобы написать чанк, вам нужно вставить новый чанк в цепочку. Если чанк с таким именем уже существует, его необходимо заменить.

См. реализацию на GitHub — функция chunkSet.

Исходный код

Функции для работы с чанками PNG находятся в одном файле. Файл не имеет зависимостей, поэтому вы можете просто скопировать его в свой проект.

png-chunk-utils.js

Пример использования:

// 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);

Листинг 2. Вызов функций для записи и чтения фрагментов PNG

Как поддержать проект

  • Начинайте пользоваться, расскажите, что думаете.
    Любым способом: комментарии, личные сообщения, на GitHub.
    Все читаю, веду список предложений.
  • Расскажи своим друзьям.
  • Ставьте звезды на GitHub.