Чтение андроида jpeg EXIF метаданные из обратного вызова изображения
Справочная информация. Я пишу приложение для камеры для программы-мессенджера. Я не могу сохранить захваченное изображение на постоянный диск в любое время. Камера должна поддерживать все ориентации. Моя реализация - это привычные примеры Surfaceview. Я использую класс Display для определения ориентации и вращения камеры соответственно. В обратном вызове takePicture jpeg я создаю растровое изображение из байта [], чтобы обойти некоторые проблемы с соотношением сторон, которые у меня были: API-интерфейс камеры: проблемы с перекрестными устройствами
Проблема Описание: На некоторых устройствах построенный битмап, сделанный на ROTATION_270 (устройство, повернутое на 90 градусов по часовой стрелке), перевернуто вверх дном. Пока что это Samsung. Я могу только предположить, что, возможно, камера спаяна с другой стороны или что-то в этом влиянии, но что ни здесь, ни там. Хотя я могу проверить, ботмад боком, я не могу логически проверить, перевернуто ли оно по размерам, поэтому мне нужен доступ к данным EXIF.
Android предоставляет синтаксический анализатор для этого http://developer.android.com/reference/android/media/ExifInterface.html, но, к сожалению, у него есть один конструктор, который принимает файл... который я не знаю, t есть и не хотят. Интуитивно я мог написать конструктор для байтового массива, но это кажется очень болезненным, учитывая их вызовы в собственный код http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2.1_r1/android/media/ExifInterface.java
Мой вопрос состоит из двух частей:
-
Кто-нибудь знает, содержит ли массив byte [] полный EXIF-заголовок jpeg
данные как есть или есть путь через BitmapFactory.decode(...)/
BitmapFactory.compress(...) добавляет, что как-то?
-
Если эти EXIF-данные выходят из массива байтов, как я могу разобрать ориентировочная информация надежным образом?
Редактировать 10/18/12
Ответ pcans ниже включает часть 2 моего вопроса. Как я указал в комментариях ниже своего ответа, если вы хотите использовать этот анализатор, вам придется включить источник в свой проект. Изменения, упомянутые в этой связанной ссылке SO, уже были сделаны и отправлены здесь: https://github.com/strangecargo/metadata-extractor
ПРИМЕЧАНИЕ. Новые версии метаданных-экстрактора работают непосредственно на Android без изменений и доступны через Maven.
Однако, что касается части 1, я получаю 0 тегов из парсера, когда я запускаю его с массивом байтов, который я получаю от takePicture. Меня беспокоит, что массив байтов не имеет необходимых мне данных. Я буду продолжать изучать это, но приветствую дальнейшее понимание.
Ответы
Ответ 1
Плохая новость:
Android Api, к сожалению, не позволит вам читать exif-данные с Stream
, только с File
.
ExifInterface не имеет конструктора с InputStream
.
Поэтому вы должны самостоятельно анализировать jpeg-контент.
Хорошая новость:
API существует для чистой Java. Вы можете использовать это: https://drewnoakes.com/code/exif/
Это Open Source, опубликованный под лицензией Apache 2 и доступный как Maven пакет.
Существует конструктор с InputStream
:
public ExifReader(java.io.InputStream is)
Вы можете создать InputStream
, поддерживаемый вашим byte[]
, с помощью ByteArrayInputStream
следующим образом:
InputStream is = new ByteArrayInputStream(decodedBytes);
Ответ 2
Чтобы прочитать метаданные /EXIF из изображения byte[]
(полезно для Camera.takePicture()
) с помощью версии 2.9.1 библиотека извлечения метаданных в Java Drew Noakes:
try
{
// Extract metadata.
Metadata metadata = ImageMetadataReader.readMetadata(new BufferedInputStream(new ByteArrayInputStream(imageData)), imageData.length);
// Log each directory.
for(Directory directory : metadata.getDirectories())
{
Log.d("LOG", "Directory: " + directory.getName());
// Log all errors.
for(String error : directory.getErrors())
{
Log.d("LOG", "> error: " + error);
}
// Log all tags.
for(Tag tag : directory.getTags())
{
Log.d("LOG", "> tag: " + tag.getTagName() + " = " + tag.getDescription());
}
}
}
catch(Exception e)
{
// TODO: handle exception
}
Чтобы прочитать ориентацию EXIF изображения (а не ориентацию миниатюры):
try
{
// Get the EXIF orientation.
final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if(exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION))
{
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
/* Work on exifOrientation */
}
else
{
/* Not found */
}
}
catch(Exception e)
{
// TODO: handle exception
}
Ориентация от 1 до 8. См. здесь, здесь, здесь или здесь.
Преобразование растрового изображения на основе его ориентации EXIF:
try
{
final Matrix bitmapMatrix = new Matrix();
switch(exifOrientation)
{
case 1: break; // top left
case 2: bitmapMatrix.postScale(-1, 1); break; // top right
case 3: bitmapMatrix.postRotate(180); break; // bottom right
case 4: bitmapMatrix.postRotate(180); bitmapMatrix.postScale(-1, 1); break; // bottom left
case 5: bitmapMatrix.postRotate(90); bitmapMatrix.postScale(-1, 1); break; // left top
case 6: bitmapMatrix.postRotate(90); break; // right top
case 7: bitmapMatrix.postRotate(270); bitmapMatrix.postScale(-1, 1); break; // right bottom
case 8: bitmapMatrix.postRotate(270); break; // left bottom
default: break; // Unknown
}
// Create new bitmap.
final Bitmap transformedBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.getWidth(), imageBitmap.getHeight(), bitmapMatrix, false);
}
catch(Exception e)
{
// TODO: handle exception
}
Ответ 3
Таким образом, используя мое предложение для редактирования и pcans, я получил данные изображения, но это было не то, что я ожидал. В частности, не все устройства будут давать ориентацию вообще. Если вы следуете этому пути, обратите внимание, что
-
"Исправленная версия Android" библиотеки ExifReader, на которую я указываю,
отредактированный 2.3.1, который является несколькими версиями старых. Новые примеры
сайт и в источнике относятся к новейшим 2.6.x, где он
значительно меняет API. Используя интерфейс 2.3.1, вы можете
выгрузите все данные EXIF из байта [], выполнив следующие действия:
Metadata header;
try {
ByteArrayInputStream bais= new ByteArrayInputStream(data);
ExifReader reader = new ExifReader(bais);
header = reader.extract();
Iterator<Directory> iter = header.getDirectoryIterator();
while(iter.hasNext()){
Directory d = iter.next();
Iterator<Tag> iterTag = d.getTagIterator();
while(iterTag.hasNext()){
Tag t = iterTag.next();
Log.e("DEBUG", "TAG: " + t.getTagName() + " : " + t.getDescription());
}
}
} catch (JpegProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MetadataException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
если вам нужны значения числовых тегов, просто замените
t.getDescription()
с
d.getInt(t.getTagType())
- Хотя ExifReader имеет конструктор с использованием байта [], я должен иметь
неправильно понимает, что он ожидает, потому что, если я попытаюсь использовать его с
массив данных напрямую, я получаю теги в возвращаемом каталоге.
Я действительно не добавлял многого, насколько это касается ответа, поэтому я принимаю ответ pcans.
Ответ 4
Если вы используете библиотеку Glide, вы можете получить ориентацию Exif из InputStream:
InputStream is=getActivity().getContentResolver().openInputStream(originalUri);
int orientation=new ImageHeaderParser(is).getOrientation();
Ответ 5
Если вам нужен способ чтения EXIF-данных, которые не будут настолько зависимы от того, откуда пришел ваш URI, вы можете использовать библиотеку поддержки exif и прочитайте его из потока. Например, я получаю ориентацию изображения.
build.gradle
dependencies {
...
compile "com.android.support:exifinterface:25.0.1"
...
}
Пример кода:
import android.support.media.ExifInterface;
...
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
ExifInterface exif = new ExifInterface(inputStream);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
} catch (IOException e) {
e.printStackTrace();
}
Причина, по которой я должен был сделать это таким образом, как только мы начали настраивать api 25 (может быть, проблема с 24+ также), но все еще поддерживая обратно в api 19, на Android-Android наше приложение потерпит крах, если я пройду в URI, который был просто ссылаясь на файл. Поэтому мне пришлось создать URI, чтобы перейти к камере, как это.
FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", tempFile);
Проблема в том, что этот файл не позволяет превратить URI в реальный путь к файлу (кроме сохранения на пути к временному файлу).
Ответ 6
Для всех, кому может быть интересно, вот как получить тег Orientation с помощью интерфейса 2.3.1 от https://github.com/strangecargo/metadata-extractor
Metadata header;
try {
ByteArrayInputStream bais= new ByteArrayInputStream(data);
ExifReader reader = new ExifReader(bais);
header = reader.extract();
Directory dir = header.getDirectory(ExifDirectory.class);
if (dir.containsTag(ExifDirectory.TAG_ORIENTATION)) {
Log.v(TAG, "tag_orientation exists: " + dir.getInt(ExifDirectory.TAG_ORIENTATION));
}
else {
Log.v(TAG, "tag_orietation doesn't exist");
}
} catch (JpegProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MetadataException e) {
e.printStackTrace();
}
Ответ 7
Если у вас есть content://
тип Uri
, андроид предоставляет API через ContentResolver
, и нет необходимости использовать внешние библиотеки:
public static int getExifAngle(Context context, Uri uri) {
int angle = 0;
Cursor c = context.getContentResolver().query(uri,
new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
null,
null,
null);
if (c != null && c.moveToFirst()) {
int col = c.getColumnIndex( MediaStore.Images.ImageColumns.ORIENTATION );
angle = c.getInt(col);
c.close();
}
return angle;
}
Вы также можете прочитать любое другое значение, которое вы найдете в MediaStore.Images.ImageColumns
, например, широта и долгота.
В настоящее время он не работает с file:///
Uris, но может быть легко изменен.