1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera;
import java.util.HashMap;
import java.util.Iterator;
/**
* Wrapper for native Exif library
*/
public class ExifInterface {
private String mFilename;
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
public static final int ORIENTATION_NORMAL = 1;
// left right reversed mirror
public static final int ORIENTATION_FLIP_HORIZONTAL = 2;
public static final int ORIENTATION_ROTATE_180 = 3;
// upside down mirror
public static final int ORIENTATION_FLIP_VERTICAL = 4;
// flipped about top-left <--> bottom-right axis
public static final int ORIENTATION_TRANSPOSE = 5;
// rotate 90 cw to right it
public static final int ORIENTATION_ROTATE_90 = 6;
// flipped about top-right <--> bottom-left axis
public static final int ORIENTATION_TRANSVERSE = 7;
// rotate 270 to right it
public static final int ORIENTATION_ROTATE_270 = 8;
// The Exif tag names
public static final String TAG_ORIENTATION = "Orientation";
static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal";
static final String TAG_MAKE = "Make";
static final String TAG_MODEL = "Model";
static final String TAG_FLASH = "Flash";
static final String TAG_IMAGE_WIDTH = "ImageWidth";
static final String TAG_IMAGE_LENGTH = "ImageLength";
static final String TAG_GPS_LATITUDE = "GPSLatitude";
static final String TAG_GPS_LONGITUDE = "GPSLongitude";
static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
private boolean mSavedAttributes = false;
private boolean mHasThumbnail = false;
private HashMap<String, String> mCachedAttributes = null;
static {
System.loadLibrary("exif");
}
public ExifInterface(String fileName) {
mFilename = fileName;
}
/**
* Given a HashMap of Exif tags and associated values, an Exif section in
* the JPG file is created and loaded with the tag data. saveAttributes()
* is expensive because it involves copying all the JPG data from one file
* to another and deleting the old file and renaming the other. It's best
* to collect all the attributes to write and make a single call rather
* than multiple calls for each attribute. You must call "commitChanges()"
* at some point to commit the changes.
*/
public void saveAttributes(HashMap<String, String> attributes) {
// format of string passed to native C code:
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
// example:
// "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
StringBuilder sb = new StringBuilder();
int size = attributes.size();
if (attributes.containsKey("hasThumbnail")) {
--size;
}
sb.append(size + " ");
Iterator<String> keyIterator = attributes.keySet().iterator();
while (keyIterator.hasNext()) {
String key = keyIterator.next();
if (key.equals("hasThumbnail")) {
// this is a fake attribute not saved as an exif tag
continue;
}
String val = attributes.get(key);
sb.append(key + "=");
sb.append(val.length() + " ");
sb.append(val);
}
String s = sb.toString();
if (android.util.Config.LOGV) {
android.util.Log.v("camera", "saving exif data: " + s);
}
saveAttributesNative(mFilename, s);
mSavedAttributes = true;
}
/**
* Returns a HashMap loaded with the Exif attributes of the file. The key
* is the standard tag name and the value is the tag's value: e.g.
* Model -> Nikon. Numeric values are returned as strings.
*/
public HashMap<String, String> getAttributes() {
if (mCachedAttributes != null) {
return mCachedAttributes;
}
// format of string passed from native C code:
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
// example:
// "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
mCachedAttributes = new HashMap<String, String>();
String attrStr = getAttributesNative(mFilename);
// get count
int ptr = attrStr.indexOf(' ');
int count = Integer.parseInt(attrStr.substring(0, ptr));
// skip past the space between item count and the rest of the attributes
++ptr;
for (int i = 0; i < count; i++) {
// extract the attribute name
int equalPos = attrStr.indexOf('=', ptr);
String attrName = attrStr.substring(ptr, equalPos);
ptr = equalPos + 1; // skip past =
// extract the attribute value length
int lenPos = attrStr.indexOf(' ', ptr);
int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
ptr = lenPos + 1; // skip pas the space
// extract the attribute value
String attrValue = attrStr.substring(ptr, ptr + attrLen);
ptr += attrLen;
if (attrName.equals("hasThumbnail")) {
mHasThumbnail = attrValue.equalsIgnoreCase("true");
} else {
mCachedAttributes.put(attrName, attrValue);
}
}
return mCachedAttributes;
}
/**
* Given a numerical orientation, return a human-readable string describing
* the orientation.
*/
public static String orientationToString(int orientation) {
// TODO: this function needs to be localized and use string resource ids
// rather than strings
String orientationString;
switch (orientation) {
case ORIENTATION_NORMAL:
orientationString = "Normal";
break;
case ORIENTATION_FLIP_HORIZONTAL:
orientationString = "Flipped horizontal";
break;
case ORIENTATION_ROTATE_180:
orientationString = "Rotated 180 degrees";
break;
case ORIENTATION_FLIP_VERTICAL:
orientationString = "Upside down mirror";
break;
case ORIENTATION_TRANSPOSE:
orientationString = "Transposed";
break;
case ORIENTATION_ROTATE_90:
orientationString = "Rotated 90 degrees";
break;
case ORIENTATION_TRANSVERSE:
orientationString = "Transversed";
break;
case ORIENTATION_ROTATE_270:
orientationString = "Rotated 270 degrees";
break;
default:
orientationString = "Undefined";
break;
}
return orientationString;
}
/**
* Copies the thumbnail data out of the filename and puts it in the Exif
* data associated with the file used to create this object. You must call
* "commitChanges()" at some point to commit the changes.
*/
public boolean appendThumbnail(String thumbnailFileName) {
if (!mSavedAttributes) {
throw new RuntimeException("Must call saveAttributes "
+ "before calling appendThumbnail");
}
mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName);
return mHasThumbnail;
}
/**
* Saves the changes (added Exif tags, added thumbnail) to the JPG file.
* You have to call saveAttributes() before committing the changes.
*/
public void commitChanges() {
if (!mSavedAttributes) {
throw new RuntimeException("Must call saveAttributes "
+ "before calling commitChanges");
}
commitChangesNative(mFilename);
}
public boolean hasThumbnail() {
if (!mSavedAttributes) {
getAttributes();
}
return mHasThumbnail;
}
public byte[] getThumbnail() {
return getThumbnailNative(mFilename);
}
public static String convertRationalLatLonToDecimalString(
String rationalString, String ref, boolean usePositiveNegative) {
try {
String [] parts = rationalString.split(",");
String [] pair;
pair = parts[0].split("/");
int degrees = (int) (Float.parseFloat(pair[0].trim())
/ Float.parseFloat(pair[1].trim()));
pair = parts[1].split("/");
int minutes = (int) ((Float.parseFloat(pair[0].trim())
/ Float.parseFloat(pair[1].trim())));
pair = parts[2].split("/");
float seconds = Float.parseFloat(pair[0].trim())
/ Float.parseFloat(pair[1].trim());
float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
String preliminaryResult = String.valueOf(result);
if (usePositiveNegative) {
String neg = (ref.equals("S") || ref.equals("E")) ? "-" : "";
return neg + preliminaryResult;
} else {
return preliminaryResult + String.valueOf((char) 186) + " "
+ ref;
}
} catch (RuntimeException ex) {
// if for whatever reason we can't parse the lat long then return
// null
return null;
}
}
public static String makeLatLongString(double d) {
d = Math.abs(d);
int degrees = (int) d;
double remainder = d - degrees;
int minutes = (int) (remainder * 60D);
// really seconds * 1000
int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D);
String retVal = degrees + "/1," + minutes + "/1," + seconds + "/1000";
return retVal;
}
public static String makeLatStringRef(double lat) {
return lat >= 0D ? "N" : "S";
}
public static String makeLonStringRef(double lon) {
return lon >= 0D ? "W" : "E";
}
private native boolean appendThumbnailNative(String fileName,
String thumbnailFileName);
private native void saveAttributesNative(String fileName,
String compressedAttributes);
private native String getAttributesNative(String fileName);
private native void commitChangesNative(String fileName);
private native byte[] getThumbnailNative(String fileName);
}
|