安卓EXIF数据始终为0,如何更改?

29
我有一个应用程序,使用本地相机捕获照片,然后将它们上传到服务器。我的问题是所有照片都具有0的EXIF方向值,这会在其他地方引起问题。
如何更改EXIF方向?我不是要找到一种可以适用于每种情况的纠正方法,只是更改为不同的值。
我正在使用三星Galaxy Note 4。
我尝试了这个解决方案,在拍照之前设置相机方向:设置Android照片EXIF方向
Camera c = Camera.open();
c.setDisplayOrientation(90);

Camera.Parameters params = mCamera.getParameters();
params.setRotation(0); // tried 0, 90, 180
c.setParameters(params);

但是它不会影响最终的EXIF数据,它仍然始终为0。
我还尝试了这些解决方案,其中图像在拍摄后被旋转:使用纵向相机应用程序拍摄的图像的EXIF方向标记值始终为0 虽然这会旋转照片,但EXIF方向仍然始终为0。
我还尝试直接设置EXIF数据:如何在Android中压缩位图后保存Exif数据
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        final File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE, "");
        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);

            ExifInterface exif = new ExifInterface(pictureFile.toString());
            exif.setAttribute(ExifInterface.TAG_ORIENTATION, "3");
            exif.saveAttributes();

            fos.write(data);
            fos.close();

            //upload photo..
        }
    }
}

但是上传后EXIF方向仍为0。

我还看过这些解决方案:

Exif数据TAG_ORIENTATION始终为0

如何在Android中写入exif数据?

如何从默认图像库中选择的图像获取正确的方向?

如何设置相机图像方向?

但它们都涉及通过旋转来矫正方向,这不会影响EXIF数据,或者直接设置EXIF数据,似乎也行不通。

我该如何将文件的EXIF方向数据从0更改为3?

更新:

这是我的上传代码:

Bitmap sBitmap = null;
final File sResizedFile = getOutputMediaFile(MEDIA_TYPE_IMAGE, "_2");
try {
    sBitmap = BitmapFactory.decodeStream(new FileInputStream(pictureFile), null, options);
} catch (FileNotFoundException e) {
    Log.e("App", "[MainActivity] unable to convert pictureFile to bitmap");
    e.printStackTrace();
    return;
}

// ... compute sw and sh int values

Bitmap sOut = Bitmap.createScaledBitmap(sBitmap, sw, sh, false);
Bitmap rotatedBitmap = rotateBitmap(sOut, 3);
FileOutputStream sfOut;
try {
    sfOut = new FileOutputStream(sResizedFile);
    rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, sfOut);
    sfOut.flush();
    sfOut.close();
    sBitmap.recycle();
    sOut.recycle();
    rotatedBitmap.recycle();
} catch (Exception e) {
    Log.e("App", "[MainActivity] unable to save thumbnail");
    e.printStackTrace();
    return;
}
// upload small thumbnail
TransferObserver sObserver = transferUtility.upload(
            "stills/small",        /* The bucket to upload to */
            filename + ".jpg",     /* The key for the uploaded object */
            sResizedFile           /* The file where the data to upload exists */
);

你是在哪个设备上尝试? - MKJParekh
三星 Galaxy Note 4 - Cbas
@Cbas,请问_"EXIF方向值为0"是在上传到在线存储桶之前还是之后?我刚测试了亚马逊的AWS存储桶,它们在上传后保留EXIF。你一定是在使用另一个基于存储桶的服务器(例如:Google Cloud),对吗?在你第一个代码片段之后,你说"它不会影响结果的EXIF数据,其仍然始终为0",所以这是代码问题吗?然后,在你的第二个代码片段之后,你说"但是,上传后EXIF方向仍然为0"。_ 所以这是上传/服务器的问题吗?请确认即使在上传之前,EXIF值是否始终为0,还是只有在上传之后才为0? - VC.One
@VC.One 在上传之前是0,肯定是代码问题。 - Cbas
5个回答

30

正如你所看到的,EXIF信息在Android上(特别是三星设备)不可靠。

但是,持有媒体对象引用的电话SQL数据库是可靠的。我建议采用这种方式。

从Uri获取方向:

private static int getOrientation(Context context, Uri photoUri) {
    Cursor cursor = context.getContentResolver().query(photoUri,
            new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);

    if (cursor.getCount() != 1) {
        cursor.close();
        return -1;
    }

    cursor.moveToFirst();
    int orientation = cursor.getInt(0);
    cursor.close();
    cursor = null;
    return orientation;
}

然后初始化旋转的Bitmap

public static Bitmap rotateBitmap(Context context, Uri photoUri, Bitmap bitmap) {
    int orientation = getOrientation(context, photoUri);
    if (orientation <= 0) {
        return bitmap;
    }
    Matrix matrix = new Matrix();
    matrix.postRotate(orientation);
    bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
    return bitmap;
}

如果你想改变图片的方向,可以尝试以下代码片段:

public static boolean setOrientation(Context context, Uri fileUri, int orientation) {
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.ORIENTATION, orientation);
    int rowsUpdated = context.getContentResolver().update(fileUri, values, null, null);
    return rowsUpdated > 0;
}
如果您设置图像的方向,则以后它将始终设置为正确的方向。稍后无需使用ExifInterface,因为图像已正确旋转。
如果此方法不令人满意,则可以尝试此方法

1
是的,你说得对,我们应该获取从相册选择的Uri数据的方向。谢谢!你的解决方案对我很有帮助!!! - Yyy
1
你的回答绝对值得超过10个投票。谢谢。 - Alban Gashi
1
非常好用。谢谢! - Jorge181
4
抱歉,发生了一个 java.lang.NullPointerException 错误,错误信息为:尝试在空对象引用上调用接口方法“int android.database.Cursor.getCount()”。 - egorikem
@jujka 很有可能你的文件不在MediaStore图像表中。在查询方向值之前,先插入它,就像这样 - amartin1911
@amartin1911 我们在回答四年前的问题时进行了一点点的“尸体编程”,这对于这次对话来说完全没有任何价值。 - undefined

12

您已将自己的答案选为解决方案。无论如何,我的抱怨只是有用的附加信息...

您答案中的“但是...”表明您现在知道问题的原因,但没有解决方法。

事实证明,我的代码能够设置EXIF数据,但是Android和iOS解释这些数据的方式存在差异。

这可能是字节顺序问题。您可以尝试通过在十六进制编辑器中打开jpeg文件并查找来手动更改Exif的字节顺序设置...

  • 字节45 78 69 66(组成“Exif”文本),后跟两个零字节00 00
  • 接下来应该是49 49(组成“II”文本),这意味着读取数据作为小端格式。
  • 如果您将其替换为4D 4D(或“MM”文本),则读取方将把数据视为大端。

在iOS上测试以查看数字是否正确。

关于此事...

然而,设置3在iOS上显示为0,图片在Chrome上向侧面翻转。
设置6 在iOS上显示为3,图片在Chrome上看起来正确。

我唯一能补充的是 Mac是大端*,而Android / PC是小端。实质上,小端从右到左读/写字节,而大端相反。

在二进制中,011表示3,而110则表示6。3和6之间的差别仅仅是这些位上的0和1的读取顺序而已。所以一个按照零-一-一的顺序读取的系统会得到3的结果,但另一个不同的字节序系统会读取相同位数为一-一-零,并告诉你结果是6。我无法解释为什么"3显示为0"没有一个测试文件来分析字节,但对我来说这是一个奇怪的结果。 </end rant>
<sleep> *注意:虽然Mac是大端模式,但经过再次检查后发现iOS实际上使用的是小端模式系统。不过你的数字仍然表明了一个大端模式与小端模式的问题。

11

事实证明我的代码可以设置EXIF数据,但是Android解释这些数据的方式与iOS以及Mac上使用Chrome(我在检查结果文件时所用的工具)的方式存在差异。

以下是设置EXIF方向所需的全部代码:

ExifInterface exif = new ExifInterface(pictureFile.toString());
exif.setAttribute(ExifInterface.TAG_ORIENTATION, "3");
exif.saveAttributes();

然而,将3设置后在iOS上显示为0,并且在Chrome中图像侧向。

6设置后在iOS上显示为3,并且在Chrome中图像显示正确。


1
+1 表示自己的问题有进展。你应该提到 Android / iOS 测试问题。我能补充的唯一一点是,iOS 是大端字节序,而 Android 是小端字节序。我会在答案框中详细说明... - VC.One

3

ExifInterface:无效图像:ExifInterface获取了一个不支持的图像格式文件(ExifInterface仅支持JPEG和一些RAW图像格式),或者是一个损坏的JPEG文件。当我运行你的代码时出现了这个错误。 - yarin

3
有一个类可以读取并更新这些图片信息。
要更新属性,您可以按以下方式使用:
ExifInterface ef = new ExifInterface(filePath);
            ef.setAttribute(MAKE_TAG, MAKE_TAG);
            ef.setAttribute(ExifInterface.TAG_ORIENTATION, orientation+"");
            ef.saveAttributes();

阅读时可以像这样使用

 ExifInterface exif = null;
        try {
            exif = new ExifInterface(absolutePath+path);
        } catch (IOException e) {
            e.printStackTrace();
        }
        int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_UNDEFINED);

我希望这可以帮助你


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接