lecture des métadonnées EXIF d'android jpeg à partir de la fonction de rappel d'image

Background: j'écris une appli caméra pour un programme de messagerie. Je ne peux pas sauvegarder l'image capturée sur le disque persistant à tout moment. La caméra doit supporter toutes les orientations. Mon implémentation est celle des exemples familiers de Surfaceview. J'utilise la classe Display pour détecter l'orientation et tourner la caméra en conséquence. Dans le takepicture jpeg callback, je construis une bitmap à partir de l'octet[] afin de contourner quelques problèmes de format d'image que j'avais: API Caméra: Cross dispositifs

Description du problème: sur certains appareils, la Bitmap construite prise à ROTATION_270 (appareil tournant à 90 degrés dans le sens des aiguilles d'une montre) vient à l'envers. Jusqu'à présent, il semble être Samsung. Je ne peux que supposer que la caméra est soudée de l'autre côté ou quelque chose qui affecte mais qui n'est ni ici ni là. Alors que je peux vérifier si un Bitmap est latéral Je ne peux pas logiquement vérifier s'il est à l'envers par dimensions donc j'ai besoin d'accéder à L'EXIF données.

Android fournit un analyseur pour ce http://developer.android.com/reference/android/media/ExifInterface.html mais malheureusement il a un constructeur unique qui accepte un fichier... je n'en ai pas et ne veux pas. Intuitivement je pourrais écrire un constructeur pour un tableau octet mais cela semble vraiment douloureux étant donné leurs appels dans le 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 donc deux parties:

  1. personne Ne sait si le byte[] tableau contient tous les EXIF jpeg en-tête de données ou est le chemin à travers les BitmapFactory.décoder.(..) / BitmapFactory.compresser.(..) en ajoutant que, en quelque sorte?

  2. si ce Les sorties de données EXIF dans le tableau byte Comment puis-je Parser de l'information fiable sur l'orientation?

Modifier 10/18/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 voulez utiliser l'analyseur, vous aurez à intégrer la source dans votre projet. Les changements mentionnés dans ce lien so post ont déjà été faits et repris ici: https://github.com/strangecargo/metadata-extractor

NOTE nouvelles versions de metadata-extractor fonctionnent directement sur Android sans modification, et sont disponibles via Maven.

Cependant, en ce qui concerne la partie 1, je récupère 0 tags de l'analyseur lorsque je l'exécute avec le tableau byte que j'obtiens de takePicture. Je commence à m'inquiéter que le byte array ne les données dont j'ai besoin. Je vais continuer à regarder dans cette mais bienvenue tout autre idée.

31
demandé sur Community 2012-10-18 02:02:28

7 réponses

la mauvaise nouvelle:

Api Android malheureusement ne vous permettra pas de lire les données exif d'un Stream , seulement à partir d'un File .

ExifInterface n'ont pas un constructeur avec un InputStream . Donc, vous devez analyser le contenu jpeg par vous-même.

la bonne nouvelle:

API existe en Java pur pour cela. Vous pouvez utiliser celui-ci: https://drewnoakes.com/code/exif/

C'est Open Source , publié sous licence Apache 2 et disponible en Maven package .

il y a un constructeur avec un InputStream : public ExifReader(java.io.InputStream is)

vous pouvez construire un InputStream soutenu par votre byte[] en utilisant un ByteArrayInputStream comme ceci:

InputStream is = new ByteArrayInputStream(decodedBytes);
18
répondu pcans 2017-01-14 15:35:22

pour lire les métadonnées / EXIF de l'image byte[] (utile pour Camera.takePicture() ) en utilisant version 2.9.1 de la metadata extraction library in 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 L'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
}

le l'orientation est de 1 à 8. Voir ici , ici , ici ou ici .


pour transformer une image bitmap basée sur 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
répondu Pang 2017-05-23 12:02:56

donc en utilisant ma suggestion edit et pcans, j'ai eu 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

  • Le "Android" fixe " ExifReader bibliothèque que je pointe est en fait le édité 2.3.1 qui est un peu renonciataires vieux. Les nouveaux exemples sur le site web et dans la source se rapportent à la nouvelle 2.6.x où il modifie l'API de manière significative. À l'aide de la 2.3.1 interface, vous pouvez supprimer toutes les données EXIF d'un octet[] en faisant ce qui 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 que ExifReader ait un constructeur utilisant byte [], je dois avoir mal compris ce qu'il attend parce que si j'essaie de l'utiliser avec le tableau de données directement, je reçois des Tags dans le répertoire retourné.

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

4
répondu Andrew G 2012-10-18 21:02:11

si vous utilisez la bibliothèque Glide, vous pouvez obtenir L'orientation Exif à partir d'une entrée:

InputStream is=getActivity().getContentResolver().openInputStream(originalUri);
int orientation=new ImageHeaderParser(is).getOrientation();
3
répondu LeandroG 2015-12-01 02:04:53

si vous voulez un moyen de lire des données EXIF qui ne sera pas si dépendante de l'endroit où votre URI est venu de vous, vous pouvez utiliser le bibliothèque de soutien exif et le lire à partir d'un flux. Par exemple, c'est comme ça que j'obtiens l'orientation de l'image.

"151930920 de construire".Grad

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 façon une fois que nous avons commencé à cibler api 25 (peut-être un problème sur 24+ aussi) mais en supportant toujours l'api 19, sur android 7 notre application se planterait si je passais dans une URI qui référençait juste un fichier. J'ai donc dû créer une URI pour passer à l'intention de la caméra comme ça.

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

le problème est que le fichier n'est pas possible de transformer L'URI en un véritable chemin de fichier (autre que le maintien du chemin de fichier temporaire).

2
répondu startoftext 2017-03-27 21:20:32

pour tous ceux qui pourraient être intéressés, voici comment obtenir la balise D'Orientation en utilisant l'interface 2.3.1 de 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
répondu Nimrod Dayan 2013-09-18 16:49:58

si vous avez un content:// type de Uri , android fournit APIs à travers ContentResolver et il n'est pas nécessaire d'utiliser des 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 actuellement pas avec file:/// Uris mais peut être facilement modifié.

0
répondu natario 2016-01-08 23:47:49