Lectura de metadatos EXIF ​​de jpeg de androide de devolución de llamada de imagen

Antecedentes: Estoy escribiendo una aplicación de cámara para un programa de mensajería. No puedo guardar la imagen capturada en disco persistente en ningún momento. La cámara debe soportar todas las orientaciones. Mi implementación es la de los conocidos ejemplos de Surfaceview. Utilizo la clase Display para detectar orientación y rotar la cámara en consecuencia. En la devolución de llamada takePicture jpeg, construyo un mapa de bits desde el byte [] para superar algunos problemas de relación de aspecto que tenía: API de cámara:

Descripción del problema: En algunos dispositivos, el mapa de bits construido tomado en ROTATION_270 (dispositivo girado 90 grados en el sentido de las agujas del reloj) viene al revés. Hasta ahora, parece ser Samsung. Sólo puedo asumir que tal vez la cámara está soldada en la otra forma o algo que afectan, pero eso no es ni aquí ni allí. Mientras que puedo comprobar si un mapa de bits es lateral no puedo comprobar lógicamente si está al revés por dimensiones así que necesito el acceso a los datos EXIF.

Android proporciona un parser para este http://developer.android.com/reference/android/media/ExifInterface.html pero desafortunadamente tiene un solo constructor que acepta un archivo … que no tengo y no quiero . Intuitively podría escribir un constructor para una matriz del byte pero eso parece realmente doloroso dado sus llamadas en 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 entonces tiene dos partes:

  1. ¿Alguien sabe si la matriz byte [] contiene datos completos de cabecera jpeg EXIF ​​como es o es la ruta a través de BitmapFactory.decode (…) / BitmapFactory.compress (…) añadiendo que de alguna manera?

  2. Si estos datos EXIF ​​salen en la matriz de bytes, ¿cómo puedo analizar la información de orientación de una manera fiable?

Edición 10/18/12

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

NOTA las nuevas versiones 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, estoy recibiendo 0 etiquetas de vuelta del analizador cuando lo corro con la matriz del byte que consigo de takePicture. Me estoy preocupando de que la matriz de bytes no tenga los datos que necesito. Continuaré estudiando esto, pero acojo cualquier nueva idea.

Las malas noticias:

Android Api tristemente no le permitirá leer datos exif de una Stream , sólo desde un File .
ExifInterface no tiene un constructor con un InputStream . Así que debe analizar el contenido jpeg por sí mismo.

Las buenas noticias:

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

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

Puede crear un InputStream respaldado por su byte[] utilizando un ByteArrayInputStream así:

 InputStream is = new ByteArrayInputStream(decodedBytes); 

Para leer metadatos / EXIF ​​from image byte[] (útil para Camera.takePicture() ) utilizando la versión 2.9.1 de la biblioteca de extracción de metadatos en Java por 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 mapa de bits basado en 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 } 

Así que con mi edición y pcans sugerencia, me dieron 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 sigue esta ruta, note que

  • El "Android fijo" librería ExifReader apunto es en realidad la versión editada 2.3.1 que es una liberación de algunos antiguos. Los nuevos ejemplos en el sitio web y en la fuente pertenecen a la 2.6.x más reciente donde cambia la API significativamente. 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<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 desea valores de etiquetas numéricas, simplemente reemplace

 t.getDescription() 

con

 d.getInt(t.getTagType()) 
  • Aunque ExifReader tiene un constructor usando byte [], debo haber entendido mal lo que espera porque si intento utilizarlo con la matriz de datos directamente, consigo a las etiquetas en el directorio devuelto.

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

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

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

Si desea una manera de leer datos EXIF ​​que no será tan dependiente de donde su URI vino de usted podría utilizar la biblioteca de soporte exif y leerlo desde una secuencia. Por ejemplo, esto es cómo obtengo la orientación de la imagen.

Construir un gradiente

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

Ejemplo de código:

 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 apuntar api 25 (tal vez un problema en 24 + también), pero todavía apoyando de nuevo a api 19, en android 7 nuestra aplicación se bloquearía si pasé en un URI que estaba haciendo referencia a un archivo. Por lo tanto tuve que crear un URI para pasar a la intención de la cámara como este.

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

El problema no es que el archivo no es posible convertir el URI en una ruta de archivo real (que no sea la celebración de la ruta del archivo temporal).

Para cualquiera que pueda estar interesado, aquí es cómo obtener la etiqueta Orientación usando la interfaz 2.3.1 desde 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 hay necesidad de 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 file:/// Uris pero se puede ajustar fácilmente.

  • ¿Cómo mostrar la imagen JPEG directamente desde la matriz de bytes (antes de guardar la imagen)?
  • Construir OpenJPEG para Android
  • Android cámara2 jpeg framerate
  • ¿Cuál es la manera más eficiente de cargar mi archivo JPG almacenado en ImageView?
  • Android: rotar una imagen de alta resolución genera un error de memoria insuficiente
  • Android / Java: Guardar una matriz de bytes en un archivo (.jpeg)
  • Cómo convertir RGB565 a YUV420SP más rápido en Android?
  • Cómo crear una imagen JPEG progresiva en Android
  • Copia de Android en el clipboard / clipdata
  • Integración o compilación de instrucciones para libjpeg-turbo en Android
  • API de acceso de decodificador JPEG HW
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.