从图片回调中读取android jpeg EXIF元数据

时间:2012-10-17 22:02:28

标签: java android jpeg exif

背景:我正在为一个信使程序编写一个相机应用程序。我无法随时将捕获的图像保存到永久磁盘。相机必须支持所有方向。我的实现是熟悉的Surfaceview示例。我使用Display类来检测方向并相应地旋转相机。在takePicture jpeg回调中,我从byte []构造一个位图,以解决我遇到的一些宽高比问题:Camera API: Cross device issues

问题描述:在某些设备上,在ROTATION_270处拍摄的构造的位图(设备顺时针旋转90度)是颠倒的。到目前为止,似乎是三星。我只能假设相机可能是通过其他方式焊接或者是那种影响,但这既不是在这里也不是在那里。虽然我可以检查Bitmap是否是横向的但我无法从逻辑上检查它是否是尺寸颠倒所以我需要访问EXIF数据。

Android为此http://developer.android.com/reference/android/media/ExifInterface.html提供了一个解析器,但不幸的是它有一个接受文件的构造函数...我没有,也不想要。直观地说,我可以为一个字节数组编写一个构造函数,但考虑到它们调用本机代码http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2.1_r1/android/media/ExifInterface.java

,这看起来真的很痛苦

我的问题有两个部分:

  1. 有没有人知道byte []数组是否包含完整的EXIF jpeg标头 数据原样或是通过BitmapFactory.decode的路径(...)/ BitmapFactory.compress(...)以某种方式添加?

  2. 如果这个EXIF数据在字节数组中退出,我该如何解析出来     方向信息以可靠的方式?

  3. 编辑10/18/12

    下面的

    pcans'回答涉及我的问题的第2部分。正如我在下面的评论中指出的那样,如果你想使用该解析器,你必须将源代码合并到你的项目中。已关联的SO帖子中提到的更改已在此处发布并重新发布:https://github.com/strangecargo/metadata-extractor

      

    注意metadata-extractor的较新版本可直接在Android上运行而无需修改,可通过Maven获取。

    然而,至于第1部分,当我使用从takePicture获得的字节数组运行时,我从解析器返回0个标签。我开始担心字节数组没有我需要的数据。我将继续研究这一点,但欢迎任何进一步的见解。

8 个答案:

答案 0 :(得分:32)

使用Camera.takePicture() metadata extraction library in JavaDrew Noakes 版本2.9.1 ,从图像byte[](对here有用)中读取元数据/ EXIF:

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
}

要阅读图片的EXIF 方向(而不是缩略图的方向):

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
}

方向是1到8.请参阅hereherehere或{{3}}。


根据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
}

答案 1 :(得分:18)

坏消息:

Android Api遗憾地不允许您仅使用StreamFile读取exif数据。
ExifInterface没有InputStream的构造函数。 所以你必须自己解析jpeg内容。

好消息:

纯Java中存在API。你可以使用这个:https://drewnoakes.com/code/exif/
它是Open Source,在Apache License 2下发布,可以Maven package的形式发布。

有一个InputStream的构造函数: public ExifReader(java.io.InputStream is)

您可以使用这样的InputStream构建byte[]支持的ByteArrayInputStream

InputStream is = new ByteArrayInputStream(decodedBytes);

答案 2 :(得分:4)

因此,使用我的编辑和数据库建议,我得到了图像数据,但它不是我所期望的。具体而言,并非所有设备都会提供方向。如果您按照此路径注意

  • 我指出的“Android修复”ExifReader库实际上是 编辑2.3.1这是一些旧版本。关于的新例子 网站和源代码属于他最新的2.6.x 显着改变API。使用2.3.1界面,您可以 通过执行以下操作从byte []转储所有EXIF数据:

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

如果您想要数字标签值,只需替换

t.getDescription()

d.getInt(t.getTagType())
  • 虽然ExifReader有一个使用byte []的构造函数,但我必须有 误解了它的期望,因为如果我试着用它 数据数组直接,我得到返回目录中的标签。

就答案而言,我真的没有多少补充,所以我接受了粉丝的回答。

答案 3 :(得分:3)

如果您使用的是Glide库,则可以从InputStream获取Exif方向:

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

答案 4 :(得分:3)

AndroidX ExifInterface支持从输入流中读取EXIF信息:

implementation "androidx.exifinterface:exifinterface:1.1.0"

然后您可以像这样将输入流传递给构造函数:

val exif = ExifInterface(inputStream)
val orientation =
        exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)

答案 5 :(得分:2)

如果您想要一种方法来读取不依赖于URI来自您的EXIF数据,您可以使用exif support library并从流中读取它。例如,这就是我如何获得图像的方向。

的build.gradle

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

示例代码:

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

一旦我们开始瞄准api 25(也许24+上也有问题)但仍支持回到api 19,我必须这样做,因为在android 7我们的应用程序会崩溃,如果我传入的URI是只是引用一个文件。因此,我必须创建一个URI来传递给相机意图。

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

问题在于该文件无法将URI转换为真正的文件路径(除了保留临时文件路径)。

答案 6 :(得分:0)

对于任何可能感兴趣的人,以下是如何使用https://github.com/strangecargo/metadata-extractor

中的2.3.1界面获取Orientation标记
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();
}

答案 7 :(得分:0)

如果你有content://类型的Uri,android会通过ContentResolver提供API,而且不需要使用外部库:

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

您还可以阅读MediaStore.Images.ImageColumns中找到的任何其他值,例如纬度和经度。

这目前不适用于file:/// Uris,但可以轻松调整。