背景:我正在为Messenger程序编写相机应用程序。我无法随时保存捕获的图像来持久磁盘。相机必须支持所有方向。我的实现是熟悉的SurfaceView示例。我使用显示类检测方向并相应地旋转相机。在ThackIcture jpeg回调中,我从字节[]构建了一个位图,以便解决我所处的某些纵横比问题:相机API:Cross Device问题
问题描述:在某些设备上,在rotation_270拍摄的构造位图(旋转90度旋转90度)颠倒了。到目前为止,它似乎是三星。我只能假设相机也许是用另一种方式焊接的,或者对影响的东西却不在这里。虽然我可以检查位图是否是侧面图,但我无法从逻辑上检查它是否通过尺寸颠倒,因此我需要访问Exif数据。
Android为此提供了一个解析器't想要。直观地,我可以为字节阵列编写一个构造函数,但是由于他们的呼叫http://grepcode.com/file/repository.grepcode.com/java/java/ext/com.google.google.android/android/android/android/2.2-2.2-2.2.1_r1/android/媒体/exifinterface.java
我的问题有两个部分:
-
有人知道字节[]数组是否包含完整的EXIF JPEG标题按原样或bitmapfactory.decode(...)/bitmapfactory.compress(...)添加以某种方式?
-
如果此EXIF数据在字节数组中退出 方向信息以可靠的方式?
编辑10/18/12
pcans的答案涉及我问题的第2部分。正如我在下面的评论中指出的那样,如果您想使用该解析器,则必须将源纳入项目中。该链接中提到的更改SO帖子已经在此处进行和重新发布:https://github.com/strangecargo/metadata-extractor
注意元数据提取器的新版本直接在Android上工作,无需修改,并且可以通过Maven提供。
但是,到第1部分,当我使用从图形上获得的字节阵列运行时,我会从解析器中获得0个标签。我开始担心字节阵列没有我需要的数据。我将继续研究这一点,但欢迎任何进一步的见解。
使用版本读取Image byte[]
(对Camera.takePicture()
)读取元数据/EXIFDrew Noakes的Java元数据提取库的2.9.1 :
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。请参阅此处,此处,此处或此处。
基于其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
}
坏消息:
Android API可悲的是,您不允许您从Stream
读取EXIF数据,仅从File
。
Exifinterface没有具有InputStream
的构造函数。因此,您必须自己解析JPEG内容。
好消息:
为此, API存在于纯Java中。您可以使用此:https://drewnoakes.com/code/exif/
它是开源的,以Apache许可2出版,并作为Maven软件包可用。
有一个带有InputStream
的构造函数: public ExifReader(java.io.InputStream is)
您可以使用这样的ByteArrayInputStream
构建由byte[]
支持的InputStream
:
InputStream is = new ByteArrayInputStream(decodedBytes);
androidx exifinterface支持从inputstream读取EXIF信息:
implementation "androidx.exifinterface:exifinterface:1.1.0"
然后,您可以将Inputstream传递到构造函数中:
val exif = ExifInterface(inputStream)
val orientation =
exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
因此,使用我的编辑和pcans建议,我获得了图像数据,但这不是我所期望的。具体来说,并非所有设备都会完全给出方向。如果您遵循此路径,请注意
-
我指出的" Android固定" Exifreader库实际上是编辑2.3.1,这是一些旧版本。关于网站和来源与最新的2.6.x有关显着更改API。使用2.3.1接口,您可以通过执行以下操作从字节[]转换所有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具有使用字节[]的构造函数,但我必须有误解了它的期望,因为如果我尝试将其与数据数组直接进入返回目录中的标签。
,就答案而言,我真的没有添加太多,所以我接受PCAN的答案。
如果您想要一种读取不会如此依赖URI来自何处的EXIF数据的方法,则可以使用EXIF支持库并从流中读取它。例如,这是我如何获得图像的方向。
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变成真实的文件路径(除了遵守临时文件路径之外)。
如果您使用的是滑行库,则可以从InputStream获得EXIF方向:
InputStream is=getActivity().getContentResolver().openInputStream(originalUri);
int orientation=new ImageHeaderParser(is).getOrientation();
对于任何有兴趣的人来说提取器
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();
}
如果您有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:///
URI不起作用,但可以轻松调整。