Подтвердить что ты не робот

Чтение андроида 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. Меня беспокоит, что массив байтов не имеет необходимых мне данных. Я буду продолжать изучать это, но приветствую дальнейшее понимание.

4b9b3361

Ответ 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, но может быть легко изменен.