Как написать 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
).