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):
- взять имя первого чанка
- если имя не совпадает с искомым
- взять длину чанка (первые 4 байта см. рис. 2)
- зная длину чанка, переместить курсор в начало чанка следующий кусок - повторяем 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 находятся в одном файле. Файл не имеет зависимостей, поэтому вы можете просто скопировать его в свой проект.
Пример использования:
// 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