Status Update
Comments
ib...@google.com <ib...@google.com>
ju...@gmail.com <ju...@gmail.com> #2
Can you please describe exactly what method call you're making on ExifInterface
? I would expect this to work for exifInterface.setAttribute(TAG_EXPOSURE_TIME, "1/1600")
but not exifInterface.setAttribute(TAG_EXPOSURE_TIME, "0.000625")
.
ib...@google.com <ib...@google.com> #3
```
val sourceExif = ExifInterface(sourceFilePath)
val destinationExif = ExifInterface(destinationFilePath)
exifTagsList
.associateWith { sourceExif.getAttribute(it) }
.forEach { (tag, value) ->
destinationExif.setAttribute(tag, value)
}
destinationExif.saveAttributes()
```
Where exifTagsList contains the tags I need, e.g. ExifInterface.TAG_EXPOSURE_TIME.
I'm not sure if it's okay to simply read all attributes as String and write them as String, but it worked fine for all tags, except this one.
The attribute value I get for `getAttribute(TAG_EXPOSURE_TIME)` is a String `6.25E-4`. This should be parsed correctly as double value of `0,000625`, which is equal to the actual photo exposure time of `1/1600`.
The problem here is IMO the lack of precision - for higher exposure times like 1/100, 1/50, etc. it works fine. It doesn't for smaller values like 1/1600 or 1/3200.
When you run this code, you will get 1/1666 instead of 1/1600, which I suppose is lack of precision, as 1/1666 = 0,0006:
`exifInterface.setAttribute(TAG_EXPOSURE_TIME, "6.25E-4")`
I tried `exifInterface.setAttribute(TAG_EXPOSURE_TIME, "1/1600")` but it didn't work for me. Probably the interface expects a value that it can parse as double. According to ExifInterface documentation:
> TAG_EXPOSURE_TIME
> Type is double.
ju...@gmail.com <ju...@gmail.com> #4
Thanks, I think part of the cause of the problem you're seeing is that exifInterface.getAttribute(TAG_EXPOSURE_TIME)
is returning "6.25E-4"
while setAttribute(TAG_EXPOSURE_TIME, value)
is ideally expecting "1/1600"
.
Turns out there is code to handle the case of passing a decimal string to setAttribute
, by converting it to a Rational
representation of { int numerator, int denominator }
, but it (arbitrarily)
Rational(double value) {
this(/* numerator= */ (long) (value * 10000), /* denominator */ 10000);
}
It looks like this code has been present for a long time, seems it was present in ExifInterface
added in 2016
So I think there's an obvious improvement we can make here:
-
Fix the
double
->Rational
conversion to support more precision. Ideally we would first try a denominator of1 / value
(since this would give an exact result in the case of this bug, and probably most other cases since shutter speeds that are less than 1 are usually1/something
), something like:Rational createRationalFromDouble(double value) { double inverse = 1 / value; if (isMathematicalInteger(value)) { return new Rational(1, (long) inverse); } // Not sure what we can do here }
For the
else
case, maybe we could do something clever with continued fractions:https://en.wikipedia.org/wiki/Continued_fraction
It feels like we should also align the format that getAttribute
returns with the format setAttribute
expects, in order to explicitly support the copying use-case you've described. That would mean changing getAttribute
to return "1/1600"
in this case. Unfortunately that is a breaking change for any existing code that is doing Double.parseDouble(exifInterface.getAttribute(TAG_EXPOSURE_TIME))
, so not feasible. If we were to introduce this it would need to be a separate method.
ap...@google.com <ap...@google.com> #5
Please also take into account values larger than 1. I think the exposure time might be for example 1,5 second or 15 seconds.
ib...@google.com <ib...@google.com> #6
ap...@google.com <ap...@google.com> #7
it would be useful to have an API with which one could copy all EXIF data directly from one file to another
This request is tracked by
na...@google.com <na...@google.com> #8
There's another curiosity here: Some 'unsigned rational' tags are returned from getAttribute(String)
in the form "x/y"
, e.g. TAG_FOCAL_LENGTH
. I dug a bit into this, and I think the 'automatically convert to floating point' behaviour was originally introduced in the framework version of ExifInterface
(which existed before the AndroidX one was forked from it) as part of
Given the constraint of maintaining this lossy format going forward:
ExifInterface
already has 'different type' getAttributeXXX(...)
'overloads' like getAttributeInt(...)
. Given these already exist, we could consider adding add a @Nullable String getAttributeRational(String)
that returns the "x/y"
format for tags with TYPE = (Unsigned) rational
. People would have to know to use it instead of getAttribute(String)
, but it at least gives 'lossless' access to the actual tag data read from the file - even for rational tags that are converted to lossy decimal/floating-point representations for backwards-compatibility..
Description
Summary:
Using ExifInterface to call saveAttributes on a WebP file corrupts the WebP file, causing it to become invalid.
Steps to Reproduce:
Use ExifInterface to load a WebP file that does not contain a VP8X header. Call saveAttributes on the ExifInterface object. Attempt to open the resulting WebP file. Expected Result: The WebP file should remain valid and openable after saving attributes.
Actual Result: The WebP file becomes corrupted and cannot be opened properly.
Root Cause Analysis:
If the WebP file does not have a VP8X header, ExifInterface writes a new VP8X header.
When the WebP file's width or height is greater than 8191, the issue arises.
The following code causes the problem:
If the width or height is greater than 8191, left-shifting causes the sign bit to be lost, turning a '1' into a '0', which results in an incorrect width or height.
Environment