I am developing an app for a custom android device. It is still early in the development and it is possible that the camera may physically be rotated 90 degrees to the rest of the device. This means that there is scope for great confusion between portrait and landscape for any images it takes. For this reason I would like absolute control over the Exif data in any images that the camera takes. The portrait vs landscape information in the camera parameters may be incorrect. For this reason I would like to be able to force a change in the Exif data inside onPictureTaken, before the image is saved. Is this possible, and if so how?
I am struggling because examples of playing with exif data seem to either work by changing camera parameters, or by working on an already saved file - so that's either too early or too late!
public void onPictureTaken(byte[] jpg_data, Camera camera)
{
// can I change exif data here?
try
{
FileOutputStream buf = new FileOutputStream(filename);
buf.write(jpg_data);
//... etc.
EDIT: Maybe I am misunderstanding something here... is there Exif data already contained within the jpg_data that gets passed to onPictureTaken? Or is it optionally added?
The standard way of writing exif data in Android is by using the ExifInterface which sadly only works on files that have already been written to disk.
If you wish to do the exif write without using a library, then you would have to do it after your FileOutputStream is finished writing the file.
If you don't mind using a library, Sanselan (http://commons.apache.org/proper/commons-imaging/) might have the ability to do it to a byte[] array, but the documentation is pretty limited.
Related
I am opening a previously saved image and writing a few attributes based on data that comes in a bit later in the process. When I save the attributes the image is overwritten(which I would expect). However, all the image data is missing and it is only header information. Am I missing something?
ExifInterface exif = new ExifInterface(m_LastPictureName);
exif.SetAttribute(ExifInterface.TagArtist, "xxxx");
exif.SaveAttributes(); // Currently this is dropping ALL image data but saving only EXIF information - why???
Thanks for having a look.
My fix was to install the Android.Support.Media.ExifInterface in place of Android.Media.ExifInterface.
I've managed to use ExifInterface to read EXIF tags/attributes (meta data) from a JPEG on my phone, and I can apparently also set attributes and save attributes. The strange thing is, if I do set+save on an image file, my app is able to get the attribute and display it. I can also verify in another app (Photo Editor on Google Play) that the EXIF-data is indeed written.
ExifInterface exif = new ExifInterface(path_to_image);
String x = exif.getAttribute("UserComment"); // here, x is always null...
exif.setAttribute("UserComment", "testtest");
exif.saveAttributes();
x = exif.getAttribute("UserComment"); // x = "testtest"
Now, the EXIF is saved to the JPEG file: see screenshot of my app.
This is also verified by Photo Editor app: see screenshot of that.
But, if I comment out set+save and just do get (on the same image as above), my app fails to get/see the attribute:
ExifInterface exif = new ExifInterface(path_to_image);
String x = exif.getAttribute("UserComment"); // x = null (although we know it isn't)
So: since the Photo Editor app can read the data, I'm doing something wrong (with the writing/saving). Also, if I re-run the set+save on the same image file, my app duplicates the same tag! Is there something more to this than simply set+save, then get?
Update: It seems the problem is device-dependent. Although the UserComment does not appear to be among the tags explicitly supported by ExifInterface, certain devices are nevertheless able to set and get the value in the tag. It works on Nexus'es, but not on my Sony Xperia. Please take a look at my code for getting (query) and setting (update) the UserComment tag in my other post on Content Providers.
Solution is to not use ExifInterface, it seems. I'm going to try https://github.com/sephiroth74/Android-Exif-Extended
Update: Even better, use Apache Sanselan, https://commons.apache.org/proper/commons-imaging/javadocs/api-release/org/apache/sanselan/Sanselan.html
Your code works for me. I think you have some simple bug, like you're getting mixed up between what files you're modifying and what files you're viewing in Photo Editor.
I am using html, tag:
<input type = "file" />
On android and on many cellulars I have the ability to get the file directly by taking a picture and save it.
How can I know (by javascript code) how did I get the picture (direcly by the camera, or by some files that on my cellular)?
I did some workarround, and found exif (http://www.nihilogic.dk/labs/exif/exif.js), but I didn't succeed using it for images loading dynamically, as the site : http://exif-viewer.com/
Need some source code examples, to understand how exif works on dynamically loaded images.
Thanks :)
I have found the solution by myself, so I want to participate it:
What I needed is translate the binary data to exif data,
so on exif.js, I added the following.
jQuery.fn.getExif = function() {
var exif;
var bin;
var bf;
bin = atob(this.attr("src").split(',')[1]);
if (bin) {
bf = new BinaryFile(bin);
}
if (bf) {
exif = EXIF.readFromBinaryFile(bf);
}
if (exif) {
this.attr("exifdata", exif);
}
return exif;
}
and use the above on code - just get any exif value I want.
The main issue is that the image should be rotated according to exif
(if, i.e. the orientation is 90 degrees clockwise, so I should rotate 90 counterclockwise, in order to fix the orientation) - No problem on most devices,
but there is a problem that persists on several devices, such as IPAD.
IPAD (or Safari - I don't know exactly where might be the problem) do me a favour, and auto rotate the image, when I am loading it from file, so it is displayed always correctly.
Now how can I know when to rotate the image and when not rotating it.
Thanks :)
If your Android app uses the device camera to take a picture and then resizes it (this is very, very common to reduce the size for upload), you might not realize that this resize operation strips the Exif metadata.
This can cause problems, especially if the device in question relies on the 'Orientation' tag to properly show the image upright.
Different Android devices handle camera/image rotation in different ways - my trusty old Nexus One seems to always rotate the image immediately post capture, so the file's native contents are always 'upright' when viewed.
However, other devices (especially Samsung phones in my testing), do not rotate the contents of the image file - rather, they set the Exif 'Orientation' tag. Whenever the image is displayed later, the relevant image code should detect the presence of the Orientation 'tag' and rotate the image appropriately. But if you have done any bitmap processing on the image and saved it to a new file, all of that Exif data is lost.
In addition to Orientation data, you might also lose other valuable metadata such as make/model, etc.
This confused me for a few weeks (image appears upright when displayed in phone gallery, but then arrives on my server with bad orientation and no apparent metadata). I'm adding this self-question here to help others. This blog post was very helpful:
Android re-size image without loosing EXIF information
As far as I can tell, there is no mechanism to persist the metadata automatically or even snapshot whatever is there and transfer in bulk.
Rather, it seems you must explicitly check for specific properties and copy them yourself to the new image file using the ExifInterface.
http://developer.android.com/reference/android/media/ExifInterface.html
So something like:
ExifInterface oldExif = new ExifInterface(oldImagePath);
String exifOrientation = oldExif.getAttribute(ExifInterface.TAG_ORIENTATION);
if (exifOrientation != null) {
ExifInterface newExif = new ExifInterface(imagePath);
newExif.setAttribute(ExifInterface.TAG_ORIENTATION, exifOrientation);
newExif.saveAttributes();
}
For the lazy ones, here's a reusable function:
public static void copyExif(String oldPath, String newPath) throws IOException
{
ExifInterface oldExif = new ExifInterface(oldPath);
String[] attributes = new String[]
{
ExifInterface.TAG_APERTURE,
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_DATETIME_DIGITIZED,
ExifInterface.TAG_EXPOSURE_TIME,
ExifInterface.TAG_FLASH,
ExifInterface.TAG_FOCAL_LENGTH,
ExifInterface.TAG_GPS_ALTITUDE,
ExifInterface.TAG_GPS_ALTITUDE_REF,
ExifInterface.TAG_GPS_DATESTAMP,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
ExifInterface.TAG_GPS_PROCESSING_METHOD,
ExifInterface.TAG_GPS_TIMESTAMP,
ExifInterface.TAG_IMAGE_LENGTH,
ExifInterface.TAG_IMAGE_WIDTH,
ExifInterface.TAG_ISO,
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
ExifInterface.TAG_ORIENTATION,
ExifInterface.TAG_SUBSEC_TIME,
ExifInterface.TAG_SUBSEC_TIME_DIG,
ExifInterface.TAG_SUBSEC_TIME_ORIG,
ExifInterface.TAG_WHITE_BALANCE
};
ExifInterface newExif = new ExifInterface(newPath);
for (int i = 0; i < attributes.length; i++)
{
String value = oldExif.getAttribute(attributes[i]);
if (value != null)
newExif.setAttribute(attributes[i], value);
}
newExif.saveAttributes();
}
As others have indicated, you must copy the Exif data from the original image to the final resized image. The Sanselan Android library is typically best for this. Depending on Android OS version, the ExifInterface sometimes corrupts the Exifdata.
In addition, the ExifInterface also handles a limited number of Exif tags -- namely only the tags that it "knows" about. Sanselan on the other hand will keep all Exiftags and marker notes.
Here is a blog post that shows how to use Sanselan for copying image data:
Copying Exif metadata using Sanselan
BTW, on Android I also tend to rotate the images and remove the Orientation Exiftag. For example, on a Nexus S with Android 4.03, the camera was setting an orientation tag in the Exifmetadata, but the webview was ignoring that information and displaying the image incorrectly. Sadly, rotating the actual image data and removing the Exiforientation tag is the only way to get every program to display images correctly.
It's already 2019 and there is still no better answer than those proposed by #prom85, Mike Repass and Theo.
In 2016, Android team introduced ExifInterface Support Library what can be the best option if you want to have consisent behaviour between Android versions. I ended up creating a subset of tags ExifInterface#EXIF_TAGS (source code) and I just iterate over this subset to extract metadata from input file and set it in an output. If you ever get a requirement to copy over all tags I recommend you to not do it! Value of some of the tags will need to be updated accordingly anyway (e.g. TAG_IMAGE_LENGTH and TAG_IMAGE_WIDTH). Personally, I kept asking questions why we need to keep all metedata data in the first place (it differs what you get between devices and camera you use anyway) and we realised that gps location and date/time data is as well need to keep.
Why not just modify the ExifInterface source and add your own implementation to support reading/writing in bulk so you don't have to specify tag by tag. Here is a snippet of what I would do.
Add new method to expose the internal attributes:
public HashMap<String, ExifAttribute>[] getAllAttributes() {
return mAttributes;
}
Add new method to set all the attributes:
public void setAttributes(HashMap<String, ExifAttribute>[] attributes) {
for (int i = 0 ; i < EXIF_TAGS.length; ++i) {
mAttributes[i] = attributes[i];
}
}
Then use it like this to preserve Exif and save to another File
// Grab all the original exif attributes from an image file and save to memory or wherever
let attributes = ExifInterface2(sourceImagePath).attributes
// Later on you can just copy those attributes to another image
ExifInterface2(destImagePath)
.setAttributes(attributes)
.saveAttributes();
I make a camera and try to capture a picture. Since the original data is YUV, I turn it into RGB using function:
static public void decodeYUV420SP(byte[] rgbBuf, byte[] yuv420sp,int width, int height)
However, the photo saved is completely black, there is no content in it.
I also found the following way:
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
but the project was shut down.
Are there any other effective ways to save a photo? Thank you!
An old post, but it speaks of a similar problem that I have so I might as well answer the part I know :)
You're probably doing it wrong. I suggest you use the JPEG callback to store the image:
mCamera.takePicture(null, null, callbackJPEG);
This way you will get JPEG data into the routine which you can store into a file unmodified:
final Camera.PictureCallback mCall = new Camera.PictureCallback()
{
#Override
public void onPictureTaken(byte[] data, Camera camera)
{
//Needs <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
File sdCard = Environment.getExternalStorageDirectory();
File file = new File(sdCard, "pic.jpg");
fil = new FileOutputStream(file);
fil.write(data);
fil.close();
}
}
As far as the black picture goes, I have found that placing a simple Thread.sleep(250) between camera.startPreview() and camera.takePicture() takes care of that particular problem on my Galaxy Nexus.
I have no idea why this delay is necessary. Even if I add camera.setOneShotPreviewCallback() and call camera.takePicture() from the callback, the image comes out black if I don't first delay...
Oh, and the delay is not just "some" delay. It has to be some pretty long value. For example, 250ms sometimes works, sometimes not on my phone.
The complete black photo is a result of immediate call to mCamera.takePicture() after calling mCamera.startPreview(). Android should be given appropriate time to process its autofocus activity before taking the actual picture. The blackness is result of erratic exposure caused due to interruption while the autofocus was happening.
I recommend calling mCamera.autoFocus() right after mCamera.startPreview().
The mCamera.takePicture() should be called in the callback function of the autofocus function call.
This flow ensures that the picture is taken after the autofocus is complete and removes blackness or exposure issues from the image taken.
The delay mentioned in Velis' answer works for some devices because those devices complete autofocus activity. Ensuring proper callback flow removes this arbitrary delay and would work on every device.
I solved this issue using following argument:
final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
When I was using TEMPLATE_STILL_CAPTURE instead of TEMPLATE_PREVIEW, which was capturing my image as full black image. This thing works in my case.