Рис 1. dgrm.net умеет открывать диаграммы из PNG картинок
Рис 1. dgrm.net умеет открывать диаграммы из PNG картинок

dgrm.net | GitHub

<< предыдущая статья

dgrm.net - это редактор диаграмм, с прицелом на трансформацию в карту знаний.

Отличительные особенности:

  • аскетичность,

  • работает на телефонах (одно из немногих web-решений),

  • открытый исходный код.

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

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

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

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

  • нет превьюшек,

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

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

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

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

PNG Chunks

Формат PNG хорошо описан на Хабре в статье “PNG — not GIF!”.

Основной момент:

  • png-файлы состоят из блоков, которые называются chunk-и,

  • в файл можно добавлять свои chunk-и.

Рис 2. Структура одного chunk-a PNG (источник “PNG — not GIF!”)
Рис 2. Структура одного chunk-a PNG (источник “PNG — not GIF!”)

Для своих данных имя chunk-a (например “dgRm”):

  • Можно придумать любое, лишь бы оно не было зарезервировано;

  • Длина имени строго 4 латинских буквы;

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

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

Чтение/запись PNG chunk-ов на JavaScript в браузере

Чтение chunk-а

Chunk-и следуют друг за другом, нужный chunk находится перебором.

Алгоритм поиска chunk-а (листинг 1):

  1. берем название первого chunk;

  2. если название не совпадает с искомым:
    a. берем длину chunk (первые 4 байта см. рис 2);
    b. зная длину chunk смещаем курсор на начало следующего chunk.

  3. повторяем 1 и 2 пока не найдем нужный chunk или '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. Функция поиска chunk-а

Краткая справка:

В JavaScript интересный способ работы с бинарными данными.

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

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

Запись chunk-а

Для записи chunk-а, нужно вставить новый chunk в цепочку. Если chunk с данным именем уже есть - его нужно заменить.

Реализацию можно посмотреть на GitHub - функция chunkSet.

Исходный код

Функции работы с PNG chunk-ами вынесены в один файл. У файла нет зависимостей, поэтому его можно просто скопировать в свой проект.
png-chunk-utils.js

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

// Запись chunk-а, на выходе новый blob
const newPngBlob = await pngChunkSet(
    // png-изображение
    pngBlob,
    // имя chunk-а
    'dgRm',
    // значение chunk-а: строка в виде массива byte
    new TextEncoder().encode('...'));
 
 
// чтение chuk-а
const dgrmChunkVal = await pngChunkGet(newPngBlob, 'dgRm');
const str = new TextDecoder().decode(dgrmChunkVal);

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

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

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

  • Расскажите знакомым.

  • Ставьте звездочки на GitHub.

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


  1. vipassa
    30.03.2022 09:40

    Да, интересная реализация. Есть над чем подумать. Спасибо


    1. merrymaker14
      30.03.2022 11:47
      +1

      Идея, правда, очень крутая, +


  1. boolive
    30.03.2022 12:03

    Кажется так делает addobe fireworks


  1. Xazzzi
    30.03.2022 16:56

    Идея напомнила давно известные rarjpg.
    При пересылке через некоторые сервисы chunk с данными может потеряться, если "лишние" данные обрезаются путем прогонки через imagemagick или что-то похожее.


    1. vsviridov
      31.03.2022 00:13
      +2

      там вроде именем чанка определяется можно ли его безопасно удалять или нет… типа если какая-то из букв заглавная — то нельзя…

      вот тут хорошо расписано:
      stackoverflow.com/a/35250354


  1. Uris
    30.03.2022 17:44

    Добротная штука получилась.


    1. Alex_BBB Автор
      31.03.2022 09:52

      Спасибо за поддержку


  1. fforthuser
    30.03.2022 19:52

    Есть такой многофункциональный редактор на Java yEd — Graph Editor
    Возможен ли какое то «сопряжение» с функциональностью реализованной в этом редакторе и Вашим проектом?


    1. Alex_BBB Автор
      31.03.2022 09:51

      Там много сделано, в ближайшей перспективе повторить маловероятно (и не уверен что надо повторять). Покажите схему которую не получается изобразить в dgrm.net - так будет проще понять конечную цель, возможно придумаем способ еще лучше чем в yEd.

      Штука интересная - особенно Isometric Drawing
      https://live.yworks.com/demos/complete/isometricdrawing/


  1. Coriolis
    31.03.2022 09:01
    +2

    Очень интересно, прям зацепило. По-быстрому набросать диаграмму и кинуть коллегам в jira.

    Не понял только - как менять размер блоков?


    1. Alex_BBB Автор
      31.03.2022 10:52

      По-быстрому набросать диаграмму и кинуть коллегам в jira

      Это задумывалось как основной сценарий: быстро набросать и что бы приятно выглядело. Пускай и с крайне ограниченными возможностями, зато просто, быстро и единообразно.

      Такая идея (минимум функционала - только самое нужное) периодически встречается, например утилита Snip. Хотя и тут перекос - если уж "только нужное" зачем и ручка и карандаш, еще какая-то линейка и палец, но нет текста

      Не понял только - как менять размер блоков?

      Сейчас не меняется. Покажите конечную цель - диаграмму которую не получается нарисовать. Может найдется вариант интереснее (а может станет очевидно что без изменения размера никуда).
      Например, с помощью размера хотите выделить фигуру - может её лучше цветом выделить или добавить возможность рисовать "маркером" (фигуру можно будет обвести).
      Если текст на фигуре не помещается - может лучше добавить еще одну дырку для ввода текста над/под/сбоку фигуры.

      Если уж размер фигур будет необходимо менять, надо придумать как это сделать. Кажется, что стандартное растягивание нарушает принцип "только нужное":

      Если растягивать, то понадобится как-то делать фигуры одинакового размера.
      Какие-то выравнивающие линии рисовать. Одну фигуру растянул, вторую добавил - опять надо растянуть. На мобильном растягивать очень не удобно. Даже просто соединять фигуры не с первого раза получается. Хотя каждый коннектор для мобильных по размеру четь не в четверть самой фигуры - и все равно промахиваешься.

      "Добавил-растянул" быстро надоест делать, захочется копировать фигуры - на desctop еще ладно Ctrl+C Ctrl+V. На мобильном как копировать? Допустим в меню фигуры кнопку "копировать" сделали, как Ctrl+V изобразить на мобильном?

      Как альтернатива растягиванию: можно предусмотреть 2-3 стандартных размера. На подобии той же Snip: выбора палитры любого цвета и прозрачности нет, есть предустановленные варианты цвета.


      1. Coriolis
        31.03.2022 11:20

        Да, причина в том что текст не помещается, поэтому захотелось растянуть. Когда делаешь "побыстрее" не получается обычно сразу коротко и лаконично изъясняться, подучаются длинные тексты. Это уже потом формулировки шлифуются или блоки декомпозируются на несколько мелких, а по началу как раз многабукаф.

        Про копипаст тоже в точку)) Второе что захотелось делать после ресайза)) После меню пробовал с зажатым Ctrl, не прокатило)))

        Да, и еще, неплохо бы варик открытия png по ссылке а не файлом, типа "Open URL" в менюшке - что бы если где-то выложили png - скопировал на неё ссылку и тут же в сервисе открыл. Ну т.е. без файлов на диске что бы обойтись.


        1. Alex_BBB Автор
          31.03.2022 11:54

          причина в том что текст не помещается, поэтому захотелось растянуть

          Дополнительная дырка ввода текста над фигурой (сейчас есть одна дырка в центре фигуры) решает проблему?

          типа "Open URL" в менюшке

          Оно?


          1. Coriolis
            31.03.2022 11:57

            1) Дырка для ввода текста поможет частично - внутри всё равно приходится сокращать слова. Хотя, может и норм. Я пример кстати в личку скинул.

            2) Нет не оно - тут копировать что бы поделиться - а я про вторую сторону процесса - вот тебе дали линк на png и ты его хочешь открыть в сервисе что бы отредактировать. (Типа Open diagram by link)


            1. Alex_BBB Автор
              31.03.2022 12:08

              2) Понял. Классная идея. Тут пока проблема: для генерации PNG по ссылке нужна серверная часть (сейчас все в браузере работает). По этой же причине такие страшные ссылки по "Copy link to diagram" - нет базы для хранения настроек диаграмм, все в url пихается. Не знаю когда проект дорастет до своей БД.


  1. aavezel
    31.03.2022 13:06

    draw.io на минималках?


    1. Alex_BBB Автор
      31.03.2022 14:36

      Похоже на то. draw.io и на мобильном работает приемлемо. Но для задачи "быстро набросал - отправил", на мой взгляд, перебор:

      обилие кнопок
      обилие кнопок

      Вкусовщина: какой-то draw.io не красивый: и сам и диаграммы его.
      Вот красивые:
      https://www.figma.com/figjam/
      https://miro.com/
      https://www.mindmeister.com/ru
      Наиболее близкий по духу (бесплатный, open sorce)
      https://www.tldraw.com/