web-dev-qa-db-fra.com

lecture de métadonnées EXIF ​​Android jpeg à partir du rappel d'image

Contexte: j'écris une application de caméra pour un programme de messagerie. Je ne peux pas enregistrer l'image capturée sur un disque persistant à tout moment. La caméra doit supporter toutes les orientations. Mon implémentation est celle des exemples bien connus de Surfaceview. J'utilise la classe Display pour détecter l'orientation et faire pivoter la caméra en conséquence. Dans le rappel jpeg de takePicture, je construis un bitmap à partir de l'octet [] afin de résoudre certains problèmes de rapport de format que je rencontrais: Camera API: problèmes liés à plusieurs appareils

Description du problème: sur certains appareils, le bitmap construit pris à ROTATION_270 (l’appareil pivoté de 90 degrés dans le sens des aiguilles d’une montre) est inversé. Jusqu'à présent, il semble que ce soit Samsung. Je peux seulement supposer que la caméra est peut-être soudée dans un sens opposé ou que quelque chose de ce genre a une incidence, mais ce n'est ni ici ni là-bas. Bien que je puisse vérifier si une image bitmap est latérale, je ne peux pas vérifier logiquement si elle est inversée par dimensions. J'ai donc besoin d'accéder aux données EXIF.

Android fournit un analyseur pour cette http://developer.Android.com/reference/Android/media/ExifInterface.html mais malheureusement, il n’a qu’un seul constructeur qui accepte un fichier ... que je n’ai pas et je ne veux pas Intuitivement, je pourrais écrire un constructeur pour un tableau d'octets, mais cela semble très pénible compte tenu de leurs appels en code natif http://grepcode.com/file/repository.grepcode.com/Java/ext/com.google.Android/android /2.2.1_r1/Android/media/ExifInterface.Java

Ma question comporte alors deux parties:

  1. Est-ce que quelqu'un sait si le tableau byte [] contient un en-tête EXIF ​​complet au format JPEG?

  2. Si ces données EXIF ​​existent dans le tableau d'octets, comment puis-je analyser le fichier informations d'orientation d'une manière fiable?

Modifier le 18/10/12

la réponse de pcans ci-dessous concerne la partie 2 de ma question. Comme je l'ai souligné dans les commentaires ci-dessous, sa réponse, si vous souhaitez utiliser cet analyseur, vous devrez incorporer la source dans votre projet. Les modifications mentionnées dans cet article SO lié ont déjà été apportées et republiées ici: https://github.com/strangecargo/metadata-extractor

REMARQUE Les versions plus récentes de metadata-extractor fonctionnent directement sur Android sans modification et sont disponibles via Maven.

Cependant, en ce qui concerne la partie 1, l'analyseur syntaxique renvoie 0 balise lorsque je l'exécute avec le tableau d'octets que je reçois de takePicture. Je crains que le tableau d'octets ne dispose pas des données dont j'ai besoin. Je continuerai à examiner la question, mais je serais heureux de recevoir des informations supplémentaires.

31
Andrew G

Les mauvaises nouvelles:

Android Api ne vous permettra malheureusement pas de lire des données exif à partir d'une Stream, mais uniquement à partir d'une File.
ExifInterface n’a pas de constructeur avec une InputStream. Vous devez donc analyser le contenu jpeg vous-même.

La bonne nouvelle:

API existe pour cela en Java pur. Vous pouvez utiliser celui-ci: https://drewnoakes.com/code/exif/
C’est Open Source , publié sous Apache License 2 et disponible sous la forme d’un paquet Maven .

Il y a un constructeur avec une InputStream: public ExifReader(Java.io.InputStream is)

Vous pouvez créer une InputStream sauvegardée par votre byte[] en utilisant une ByteArrayInputStream comme ceci: 

InputStream is = new ByteArrayInputStream(decodedBytes);
18
pcans

Pour lire les métadonnées/EXIF ​​de l'image byte[] (utile pour Camera.takePicture() ) à l'aide de version 2.9.1 de la bibliothèque d'extraction de métadonnées en Java par 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
}

Pour lire le EXIF ​​orientation de l'image (pas l'orientation de la vignette):

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
}

L'orientation est comprise entre 1 et 8. Voir ici , ici , ici ou ici .


Pour transformer une image bitmap en fonction de son orientation 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
}
32
Pang

Donc, en utilisant mes suggestions d'édition et de pcans, j'ai obtenu les données de l'image mais ce n'était pas ce à quoi je m'attendais. Plus précisément, tous les appareils ne donneront pas une orientation du tout. Si vous suivez ce chemin, notez que

  • La bibliothèque ExifReader "corrigée Android" à laquelle je fais référence est en fait le 2.3.1 édité, qui est un peu ancien. Les nouveaux exemples du site Web et du code source concernent le plus récent des 2.6.x où il modifie l'API de manière significative. À l'aide de l'interface 2.3.1, vous pouvez Vider toutes les données EXIF ​​d'un octet [] en procédant comme suit:

            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();
            }
    

si vous voulez des valeurs numériques, remplacez simplement

t.getDescription()

avec

d.getInt(t.getTagType())
  • Bien qu'ExifReader ait un constructeur utilisant byte [], je dois avoir mal compris ce à quoi il s'attend, car si j'essaie de l'utiliser directement avec le tableau.

Je n'ai pas vraiment ajouté grand chose en ce qui concerne la réponse, alors j'accepte la réponse de pcans.

4
Andrew G

Si vous utilisez la bibliothèque Glide, vous pouvez obtenir l'orientation Exif à partir d'un InputStream:

InputStream is=getActivity().getContentResolver().openInputStream(originalUri);
int orientation=new ImageHeaderParser(is).getOrientation();
3
LeandroG

Si vous voulez un moyen de lire des données EXIF ​​qui ne dépendent pas tellement de la provenance de votre URI, vous pouvez utiliser le répertoire exif support library et le lire à partir d’un flux. Par exemple, voici comment je reçois l’orientation de l’image. 

build.gradle

dependencies {
...    
compile "com.Android.support:exifinterface:25.0.1"
...
}

Exemple de code:

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();
    }

La raison pour laquelle j’ai dû le faire de cette manière une fois que nous avons commencé à cibler l’API 25 (peut-être un problème sur 24 ans également), mais en prenant en charge le retour à l’API 19, notre application se planterait même si je passais dans un fichier. Par conséquent, je devais créer un URI pour passer à l'objectif de la caméra comme celui-ci.

FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", tempFile);

Le problème avec ce fichier est qu'il n'est pas possible de transformer l'URI en un chemin de fichier réel (autre que de conserver le chemin du fichier temporaire). 

2
startoftext

Si vous avez un type content:// de Uri, Android fournit des API via ContentResolver et vous n'avez pas besoin d'utiliser de bibliothèques externes:

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;
}

Vous pouvez également lire toute autre valeur que vous trouvez dans MediaStore.Images.ImageColumns, comme la latitude et la longitude.

Cela ne fonctionne pas actuellement avec file:/// Uris mais peut être facilement modifié.

0
natario

Pour ceux qui pourraient être intéressés, voici comment obtenir la balise Orientation à l'aide de l'interface 2.3.1 depuis 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();
}
0
Nimrod Dayan