Abbreviated jpeg в Java
Я думаю все, кто хоть раз запускал компьютер, знают, что такое jpeg. Этот стандарт появился в далёком 1991 году и с тех пор оброс различными расширениями и дополнительными возможностями. Для обычных пользователей все эти изменения не видны. Вы просто кликаете по файлу и видите красивую картинку. Для 99% разработчиков jpeg также представляет собой чёрный ящик. Максимум, с чем может столкнуться среднестатистический разработчик - это оптимизировать размер картинок с помощью jpegtran или сконвертировать один формат в другой с помощью ImageIO.
Всё становится значительно сложнее, когда нужно получить картинку со спутника. Понятное дело, никто не будет придумывать совершенно новый способ кодирования картинок, только для того, чтобы передать фотографию. Но и обычные файлы для этого не подходят. Это, прежде всего, связно с шириной канала и возможными потерями сигнала.
Одним из способов передачи/хранения картинки является “Abbreviated image”. Однако, прежде, чем пытаться понять, что это такое, необходимо вспомнить формат хранения jpeg в обычном файле.
Обычный файл
Если вкратце, то данные в jpeg файле хранятся с использованием маркеров. Маркер - это специальные 2 байта, которые идентифицируют тип блока и данные в нём. Например, структура обычного jpeg файла может быть следующая:
В данном примере, SOI - это маркер начала изображения, DQT - это маркер начала таблицы квантования, SOS - это маркер начала сканирования. Он содержит небольшую метаинформацию и непосредственно данные изображения. EOI - это маркер конца изображения.
В стандарте описано большое количество различных маркеров и их данных. Здесь наиболее интересные блоки - это таблицы квантования и таблицы кодирования хаффмана. В спецификации jpeg описаны рекомендованные значения для этих таблиц. Однако, ничто не мешает в каждом файле иметь свои собственные таблицы. Например, можно подобрать более оптимальные таблицы в зависимости от количества цветов в данном изображении.
Abbreviated jpeg
Идея Abbreviated jpeg достаточно простая: выкинуть все таблицы из файла и хранить только данные изображения.
При этом, чтобы декодировать и показать изображение из этих файлов, необходимо заранее договориться о значениях в таблицах и других заголовках.
У такого способа хранения есть как свои достоинства, так и недостатки.
К достоинствам прежде всего следует отнести то, что размер файла чуть меньше. Также такие файлы устойчивы к повреждениям. Если отсутствует кусок файла или он повреждён, то достаточно найти следующий маркер RST и продолжить декодирование.
Недостатков также несколько. Во-первых, необходимо заранее договориться о параметрах файла. Это значит, что такой способ не подходит для web. Производители web браузеров совершенно точно не будут кодировать всевозможные таблицы для всех возможных комбинаций файлов. Во-вторых, современные каналы связи и диски достаточно большие, чтобы экономить на размере таблиц.
Несмотря на это, такой способ хранения и передачи изображений jpeg используется в узкоспециализированных системах. Например, формат ssdv использует Abbreviated image.
Поддержка в Java
Я был сильно удивлён, когда узнал что поддержка такого экзотичного типа хранения jpeg файла поддерживается в java. В официальной документации очень детально описано, что происходит при вызове различных методов API. К сожалению, за целый день пристального вглядывания в документацию, я так и не понял как же работать с таким форматом. Лишь на следующий день я решил бросить это дело и просто почитать исходники. Их оказалось, на удивление, не так много, и дело сдвинулось с мёртвой точки.
Итак, для того, чтобы прочитать такой файл нужно сделать 2 вещи. Во-первых, необходимо задать таблицы, о которых заранее договорились поставщик и получатель.
JPEGImageReadParam param = new JPEGImageReadParam();
param.setDecodeTables(qTables, DCHuffmanTables, ACHuffmanTables);
Во-вторых, необходимо передать эти параметры при чтении из файла:
ImageReader jpgReader = ImageIO.getImageReadersByFormatName("jpg").next();
jpgReader.setInput(new FileImageInputStream(new File("test.jpeg")));
BufferedImage image = jpgReader.read(0, param);
В результате BufferedImage
будет содержать вполне обычное изображение, которое можно сохранить или преобразовать далее.
Несмотря на всё это, API есть куда расти. Например, в JPEGImageReadParam
нельзя задать subsampling mode и ColorSpace
. А это значит, что они должны быть в самом Abbreviated image. Это в свою очередь сводит на нет, устойчивость к повреждениям файла.