36

Background: I am writing a camera app for a messenger program. I cannot save the captured image to persistent disk at any time. The camera must support all orientations. My implementation is that of the familiar Surfaceview examples. I use the Display class to detect orientation and rotate the camera accordingly. In the takePicture jpeg callback, I construct a bitmap from the byte[] in order to get around some aspect ratio issues I was having: Camera API: Cross device issues

Problem Description: On some devices, the constructed Bitmap taken at ROTATION_270 (device rotated 90 degrees clockwise) comes in upside down. So far, it seems to be Samsung. I can only assume that maybe the camera is soldered on the other way or something to that affect but that's neither here nor there. While I can check if a Bitmap is sideways I can't logically check if it is upside down by dimensions so I need access to the EXIF data.

Android provides a parser for this http://developer.android.com/reference/android/media/ExifInterface.html but unfortunately it has a single constructor which accepts a file... which I don't have and don't want. Intuitively I could write a constructor for a byte array but that seems really painful given their calls into native code http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2.1_r1/android/media/ExifInterface.java

My question then has two parts:

  1. Does anyone know if the byte[] array contains full EXIF jpeg header data as is or is the path through the BitmapFactory.decode(...) / BitmapFactory.compress(...) adding that somehow?

  2. If this EXIF data exits in the byte array how can I parse out the orientation information in a dependable manner?

Edit 10/18/12

pcans' answer below involves part 2 of my question. As I pointed to in the comments below his answer, if you want to use that parser you'll have to incorporate the source into your project. The changes mentioned in that linked SO post have already been made and reposted here: https://github.com/strangecargo/metadata-extractor

NOTE newer versions of metadata-extractor work directly on Android without modification, and are available via Maven.

However, as to part 1, I'm getting 0 tags back from the parser when I run it with the byte array I get from takePicture. I'm becoming concerned that the byte array doesn't have the data I need. I will continue to look into this but welcome any further insight.

Community
  • 1
  • 1
Andrew G
  • 1,547
  • 1
  • 13
  • 27
  • I have made available a Java metadata library for Android which can extract and insert metadata (Exif, XMP, IPTC, ICC_Profile, Photoshop IRB etc) for images like JPEG, TIFF, PNG, GIF etc. Check it [here](https://github.com/dragon66/pixymeta-android) – dragon66 Jul 01 '15 at 15:28
  • Article about [reading and writing EXIF](http://onetouchcode.com/2016/08/23/write-exif-data-image-android/) to an image file in android – Shailendra Aug 26 '16 at 09:27
  • That metadata-extractor link needs a .com: https://drewnoakes.com/code/exif/ – user4851 Nov 07 '17 at 17:48

8 Answers8

34

To read metadata/EXIF from image byte[] (useful for Camera.takePicture()) using version 2.9.1 of the metadata extraction library in Java by 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
}

To read the EXIF orientation of the image (not the orientation of the thumbnail):

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
}

The orientation is from 1 to 8. See here, here, here or here.


To transform a bitmap based on its EXIF orientation:

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
}
Community
  • 1
  • 1
Pang
  • 9,564
  • 146
  • 81
  • 122
  • 1
    More recent versions can be found at the git repository: https://github.com/drewfarris/metadata-extractor – dwbrito Aug 19 '14 at 13:11
  • 1
    @Pang this is for main camera, what about rotation for Front camera? – Muhammad Umar Jun 13 '15 at 20:33
  • 4
    @dwbrito, actually the original project author (me) has since moved the project to GitHub. The one you link to is several years old now and missing many fixes/improvements. https://github.com/drewnoakes/metadata-extractor/ – Drew Noakes May 25 '16 at 10:21
20

The bad news:

Android Api sadly won't allow you to read exif data from a Stream, only from a File.
ExifInterface don't have a constructor with an InputStream. So you must parse jpeg content by yourself.

The good news:

API exists in pure Java for this. You can use this one: https://drewnoakes.com/code/exif/
It's Open Source, published under Apache Licence 2 and available as a Maven package.

There is a constructor with an InputStream: public ExifReader(java.io.InputStream is)

You can build an InputStream backed by your byte[] using a ByteArrayInputStream like this:

InputStream is = new ByteArrayInputStream(decodedBytes);
Boris
  • 4,327
  • 2
  • 19
  • 27
pcans
  • 7,611
  • 3
  • 32
  • 27
  • Awesome. I couldn't find one under Apache. Now I'll just hope that byte array has the info in in that I want! I'll give it a shot now – Andrew G Oct 18 '12 at 14:32
  • Dumb question maybe but would there be any reason this wouldn't work on Android? I've been unable to get the library to be detected even though I've gone through the usual process of putting the jar in the libs folder and adding it to build path... – Andrew G Oct 18 '12 at 19:13
  • Sorry, that was a dumb question - I should have searched more thoroughly http://stackoverflow.com/questions/2536194/android-image-exif-reader-3rd-party-api – Andrew G Oct 18 '12 at 19:27
  • 1
    @AndrewG the library works directly on Android. There were older versions which were not compatible, but that was a while ago now (as was your question -- I mention this for others browsing these parts). – Drew Noakes May 25 '16 at 10:33
  • 3
    Android API 24 `ExifInterface` added support for `InputStream`. Source: https://developer.android.com/reference/android/media/ExifInterface.html#ExifInterface(java.io.InputStream) – Ivo Renkema Oct 26 '16 at 06:47
  • 2
    Link to library is dead – Display name Oct 27 '16 at 08:43
  • There is a recently added support library for this incase you want to read exif data from a stream on api older than 24. https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html – startoftext Mar 21 '17 at 22:44
  • `ExifInterface` does now have an `InputStream` constructor as well as `File`, `String` filename, and `FileDescriptor`. – hippietrail Aug 25 '19 at 14:47
  • ExifInterface from androidx works for api older than 24 – Tamim Attafi May 23 '21 at 16:08
7

AndroidX ExifInterface supports reading EXIF information from an inputstream:

implementation "androidx.exifinterface:exifinterface:1.1.0"

You can then just pass the inputstream into the constructor like that:

val exif = ExifInterface(inputStream)
val orientation =
        exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
teh.fonsi
  • 3,040
  • 2
  • 27
  • 22
  • This is the correct answer in 2020. No need for external libs anymore. Just your Java looks like javascript some how ... – CaptainCrunch Oct 08 '20 at 19:33
  • yes, it's working solution! val inputStream: InputStream? = contentResolver.openInputStream(uri) inputStream?.run { exif = ExifInterface(inputStream) } – Oleksandr Bodashko May 06 '21 at 15:12
4

So using my edit and pcans suggestion, I got the image data but it was not what I expected. Specifically, not all devices will give an orientation at all. If you follow this path note that

  • The "Android fixed" ExifReader library I point to is actually the edited 2.3.1 which is a few releasees old. The new examples on the website and in the source pertain to the newest 2.6.x where he changes the API significantly. Using the 2.3.1 interface, you can dump all EXIF data from a byte[] by doing the following:

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

if you want numerical tag values, simply replace

t.getDescription()

with

d.getInt(t.getTagType())
  • Although ExifReader has a constructor using byte[], I must have misunderstood what it expects because if I try to use it with the data array directly, I get to Tags in the returned directory.

I really didn't add much as far as the answer is concerned so I'm accepting pcans' answer.

Andrew G
  • 1,547
  • 1
  • 13
  • 27
4

If you want a way to read EXIF data that wont be so dependent on where your URI came from you you could use the exif support library and read it from a stream. For example this is how I get the orientation of the image.

build.gradle

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

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

The reason I had to do it this way once we started targeting api 25 (maybe a problem on 24+ also) but still supporting back to api 19, on android 7 our app would crash if I passed in a URI that was just referencing a file. Hence I had to create a URI to pass to the camera intent like this.

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

The issue there is that file its not possible to turn the URI into a real file path (other than holding on to the temp file path).

startoftext
  • 3,846
  • 7
  • 40
  • 49
3

If you are using Glide library you can get the Exif orientation from an InputStream:

InputStream is=getActivity().getContentResolver().openInputStream(originalUri);
int orientation=new ImageHeaderParser(is).getOrientation();
LeandroG
  • 831
  • 9
  • 17
  • 1
    This method did not work for me for the byte array returned in Camera.PictureCallback.onPictureTaken when the image is captured via mCamera.takePicture(null, null, mPictureCallback). In this case, orientation was -1 indicating a failure. – Andrew Dec 10 '15 at 06:11
0

For anybody who might be interested, here is how to get the Orientation tag using the 2.3.1 interface from 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();
}
Nimrod Dayan
  • 3,050
  • 4
  • 29
  • 45
0

If you have a content:// type of Uri, android provides APIs through ContentResolver and there’s no need to use external libraries:

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

You can also read any other value you find in MediaStore.Images.ImageColumns, like latitude and longitude.

This currently doesn’t work with file:/// Uris but can be easily tweaked.

natario
  • 24,954
  • 17
  • 88
  • 158
  • Doesn't work at least on some devices. Returns `col = -1` for me. – Aleks N. Nov 16 '16 at 16:20
  • @AleksN this works only if the picture was added with the correct tags to the MediaStore. Also, it must be a content uri and not a file uri. Anyway I have since changed my mind, I’d recommend using an Exif reader and leave alone content resolver queries. That works with both file and content uris. – natario Nov 16 '16 at 16:24