leyendo los metadatos de android jpeg EXIF ​​desde la callback de la imagen

Antecedentes: estoy escribiendo una aplicación de cámara para un progtwig de mensajería. No puedo guardar la imagen capturada en un disco persistente en cualquier momento. La cámara debe ser compatible con todas las orientaciones. Mi implementación es la de los ejemplos familiares de Surfaceview. Uso la clase Display para detectar la orientación y girar la cámara en consecuencia. En la callback jpeg takePicture, construyo un bitmap a partir del byte [] para solucionar algunos problemas de relación de aspecto que tenía: Camera API: problemas entre dispositivos

Descripción del problema: en algunos dispositivos, el bitmap construido tomado en ROTATION_270 (dispositivo girado 90 grados en el sentido de las agujas del reloj) aparece al revés. Hasta ahora, parece ser Samsung. Solo puedo suponer que tal vez la cámara está soldada por el otro lado o algo así, pero eso no es ni aquí ni allá. Si bien puedo verificar si un bitmap es lateral, no puedo verificar lógicamente si está al revés por dimensiones, así que necesito acceder a los datos EXIF.

Android proporciona un analizador para este http://developer.android.com/reference/android/media/ExifInterface.html pero desafortunadamente tiene un único constructor que acepta un archivo … que no tengo y no quiero . Intuitivamente podría escribir un constructor para una matriz de bytes, pero eso parece realmente doloroso debido a sus llamadas al código nativo http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2 .1_r1 / android / media / ExifInterface.java

Mi pregunta tiene dos partes:

  1. ¿Alguien sabe si la matriz byte [] contiene datos completos de encabezado jpeg EXIF ​​como está o la ruta a través de BitmapFactory.decode (…) / BitmapFactory.compress (…) agrega eso de alguna manera?

  2. Si esta información EXIF ​​sale en la matriz de bytes, ¿cómo puedo analizar la información de orientación de manera confiable?

Editar 18/10/12

La respuesta de pcans a continuación incluye la parte 2 de mi pregunta. Como señalé en los comentarios debajo de su respuesta, si desea usar ese analizador, deberá incorporar la fuente en su proyecto. Los cambios mencionados en esa publicación SO vinculada ya se han realizado y vuelto a publicar aquí: https://github.com/strangecargo/metadata-extractor

NOTA: las versiones más nuevas de metadata-extractor funcionan directamente en Android sin modificaciones, y están disponibles a través de Maven.

Sin embargo, en cuanto a la parte 1, recibo 0 tags del analizador cuando lo ejecuto con la matriz de bytes que obtengo de takePicture. Me preocupa que la matriz de bytes no tenga los datos que necesito. Continuaré investigando esto, pero agradezco cualquier idea adicional.

Las malas noticias:

Android Api lamentablemente no le permitirá leer datos exif de un Stream , solo desde un File .
ExifInterface no tiene un constructor con un InputStream . Por lo tanto, debe analizar el contenido de jpeg usted mismo.

Las buenas noticias:

API existe en Java puro para esto. Puede usar este: https://drewnoakes.com/code/exif/
Es de código abierto , publicado bajo Apache License 2 y disponible como un paquete Maven .

Hay un constructor con un InputStream : public ExifReader(java.io.InputStream is)

Puedes construir un InputStream respaldado por tu byte[] usando un ByteArrayInputStream como este:

 InputStream is = new ByteArrayInputStream(decodedBytes); 

Para leer los metadatos / EXIF ​​del byte[] imagen byte[] (útil para Camera.takePicture() ) usando la versión 2.9.1 de la biblioteca de extracción de metadatos en Java de 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 } 

Para leer la orientación EXIF de la imagen (no la orientación de la miniatura):

 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 } 

La orientación es de 1 a 8. Vea aquí , aquí , aquí o aquí .


Para transformar un bitmap en función de su orientación 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 } 

Entonces, usando mi sugerencia de edición y navegación, obtuve los datos de la imagen pero no era lo que esperaba. Específicamente, no todos los dispositivos darán una orientación en absoluto. Si sigues este camino nota que

  • La biblioteca ExifReader “arreglada para Android” a la que apunto es en realidad la versión 2.3.1 editada, que tiene algunas versiones antiguas. Los nuevos ejemplos en el sitio web y en la fuente pertenecen al último 2.6.x donde cambia significativamente el API. Usando la interfaz 2.3.1, puede volcar todos los datos EXIF ​​de un byte [] haciendo lo siguiente:

      Metadata header; try { ByteArrayInputStream bais= new ByteArrayInputStream(data); ExifReader reader = new ExifReader(bais); header = reader.extract(); Iterator iter = header.getDirectoryIterator(); while(iter.hasNext()){ Directory d = iter.next(); Iterator 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 quiere valores de tags numéricas, simplemente reemplace

 t.getDescription() 

con

 d.getInt(t.getTagType()) 
  • Aunque ExifReader tiene un constructor que usa byte [], debo haber entendido mal lo que espera porque si trato de usarlo directamente con el conjunto de datos, obtengo las tags en el directorio devuelto.

Realmente no agregué mucho en cuanto a la respuesta, así que estoy aceptando la respuesta de pcans.

Si está utilizando la biblioteca Glide, puede obtener la orientación Exif de un InputStream:

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

Si desea una forma de leer datos EXIF ​​que no dependan tanto del origen de su URI, podría usar la biblioteca de soporte exif y leerla de una secuencia. Por ejemplo, así es como obtengo la orientación de la imagen.

build.gradle

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

Código de ejemplo:

 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 razón por la que tuve que hacerlo de esta manera una vez que comenzamos a api 25 (tal vez un problema en 24+ también) pero aún soportando de nuevo a api 19, en android 7 nuestra aplicación fallaría si pasara en un URI que solo estaba haciendo referencia a un archivo. Por lo tanto, tuve que crear un URI para pasar a la intención de la cámara de esta manera.

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

El problema allí es que no es posible convertir el URI en una ruta de archivo real (que no sea mantener la ruta del archivo temporal).

Para cualquiera que pueda estar interesado, aquí está cómo obtener la etiqueta de Orientación usando la interfaz 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(); } 

Si tiene un content:// tipo de Uri , Android proporciona API a través de ContentResolver y no es necesario utilizar bibliotecas externas:

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

También puede leer cualquier otro valor que encuentre en MediaStore.Images.ImageColumns , como latitud y longitud.

Esto actualmente no funciona con el file:/// Uris, pero se puede ajustar fácilmente.