Как написать Exif в JPEG с помощью класса ExifWriter TwelveMonkey

Я использую библиотеку TwelveMonkey для чтения данных Exif из jpeg, например:

       try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
            List<JPEGSegment> exifSegment = JPEGSegmentUtil.readSegments(stream, JPEG.APP1, "Exif");
            InputStream exifData = exifSegment.get(0).data();
            exifData.read(); // Skip 0-pad for Exif in JFIF
            try (ImageInputStream exifStream = ImageIO.createImageInputStream(exifData)) {
                return new EXIFReader().read(exifStream);
            }
       }

поэтому у меня есть CompoundDirectory с кучей Entry элементы. Но как мне использовать ExifWriter в формате JPEG Использование его для записи в выходной поток просто повреждает JPEG (зрители изображения думают, что это сломанный tiff).

Обновление: что мне нравится делать, это читать JPEG в BufferedImageтакже считывание exif-данных, масштабирование и последующее сжатие в jpeg для сохранения exif-данных (т. е. запись ранее считанных данных в jpeg с уменьшенным масштабом). Для этого я сейчас использую какую-то многословную версию ImageIO методы. Вот основной код, чтобы сделать это в настоящее время: https://gist.github.com/patrickfav/5a51566f31c472d02884 (Exif Reader работает, писатель, конечно, не)

1 ответ

Решение

Пакет TwifonMonkeys Exif (EXIFReader/EXIFWriter) достаточно низкого уровня и разработан для того, чтобы ImageReader/ImageWriter Реализации. Он по-прежнему полностью применим в качестве пакета метаданных общего назначения, но может потребовать от вас дополнительной работы и некоторых знаний о формате контейнера, используемого для переноса данных Exif.

Чтобы записать данные Exif в JPEG, вам нужно написать APP1/Exif сегмент как часть нормальной структуры JIF. EXIFWriter запишет данные, которые вы должны поместить только в этот сегмент. Все остальное должно быть предоставлено вами.

Есть несколько способов достижения этого. Вы можете работать с JPEG на двоичном / потоковом уровне, или вы можете изменить данные изображения и использовать метаданные ImageIO для записи Exif. Я опишу процесс написания Exif с использованием IIOMetadata учебный класс.

Из спецификации метаданных формата JPEG и примечаний по использованию:

(Обратите внимание, что приложение, желающее интерпретировать метаданные Exif с учетом древовидной структуры метаданных в javax_imageio_jpeg_image_1.0 формат должен проверить на наличие unknown сегмент маркера с тегом, обозначающим APP1 маркер и содержит данные, идентифицирующие его как сегмент маркера Exif. Затем он может использовать специфичный для приложения код для интерпретации данных в сегменте маркера. Если такое приложение столкнется с деревом метаданных, отформатированным в соответствии с будущей версией формата метаданных JPEG, сегмент маркера Exif может быть неизвестен в этом формате - он может быть структурирован как дочерний узел JPEGvariety узел. Таким образом, для приложения важно указать, какую версию использовать, передав строку, идентифицирующую версию, методу / конструктору, используемому для получения IIOMetadata объект.)

EXIFReader будет вашим "кодом приложения для интерпретации данных". Таким же образом, вы должны быть в состоянии вставить unknown узел сегмента маркера с APP1 (обычно это было бы 0xFFE1, но в метаданных ImageIO используется только десятичное представление последнего октета в виде строки, поэтому значение "225"). Использовать ByteArrayOutputStream и записать в него данные Exif и передать полученный байтовый массив в узел метаданных как "пользовательский объект".

IIOMetadata metadata = ...; 

IIOMetadataNode root = new IIOMetadataNode("javax_imageio_jpeg_image_1.0");
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
root.appendChild(markerSequence);

Collection<Entry> entries = ...; // Your original Exif entries

// Write the full Exif segment data
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
// APPn segments are prepended with a 0-terminated ASCII identifer
bytes.write("Exif".getBytes(StandardCharsets.US_ASCII));
bytes.write(new byte[2]); // Exif uses 0-termination + 0 pad for some reason
// Finally write the EXIF data
new EXIFWriter().write(entries, new MemoryCacheImageOutputStream(bytes));

// Wrap it all in a meta data node
IIOMetadataNode exif = new IIOMetadataNode("unknown");
exif.setAttribute("MarkerTag", String.valueOf(0xE1)); // APP1 or "225"
exif.setUserObject(bytes.toByteArray());

// Append Exif data 
markerSequence.appendChild(exif);

// Merge with original data 
metadata.mergeTree("javax_imageio_jpeg_image_1.0", root);

Если ваши исходные метаданные уже содержат сегмент Exif, вероятно, лучше использовать:

// Start out with the original tree
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree("javax_imageio_jpeg_image_1.0");
IIOMetadataNode markerSequence = (IIOMetadataNode) root.getElementsByTagName("markerSequence").item(0); // Should always a single markerSequence

...

// Remove any existing Exif, or make sure you update the node, 
// to avoid having two Exif nodes
// Logic for creating the node as above

...

// Replace the tree, instead of merging
metadata.setFromTree("javax_imageio_jpeg_image_1.0", root);

Мне особенно не нравится ImageIO из-за крайней многословности кода, но я надеюсь, что вы получите представление о том, как достичь своей цели.:-)


PS: Причина, по которой зрители думают, что ваше изображение - это TIFF, заключается в том, что данные Exif - это структура TIFF. Если вы записываете только данные Exif из JPEG в другой пустой файл, у вас будет файл TIFF без данных изображения в IFD0 (и, возможно, миниатюру в IFD1).

Другие вопросы по тегам