1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.util;
18
19import android.hardware.camera2.CaptureResult;
20import android.location.Location;
21import android.os.Build;
22
23import com.android.camera.exif.ExifInterface;
24import com.android.camera.exif.Rational;
25import com.android.camera.one.v2.camera2proxy.CaptureResultProxy;
26import com.android.camera.processing.imagebackend.TaskImageContainer;
27import com.google.common.base.Optional;
28
29import java.text.DecimalFormat;
30import java.util.Calendar;
31import java.util.Date;
32import java.util.TimeZone;
33
34/**
35 * Exif utility functions.
36 */
37public class ExifUtil {
38    private static final double LOG_2 = Math.log(2); // natural log of 2
39
40    private final ExifInterface mExif;
41
42    /**
43     * Construct a new ExifUtil object.
44     * @param exif The EXIF object to populate.
45     */
46    public ExifUtil(ExifInterface exif) {
47        mExif = exif;
48    }
49
50    /**
51     * Populate the EXIF object with info pulled from a given capture result.
52     *
53     * @param image A {@link TaskImageContainer.TaskImage} from which to extract info from.
54     * @param captureResult A {@link CaptureResultProxy} from which to extract info from.
55     * @param location optionally a location that should be added to the EXIF.
56     */
57    public void populateExif(Optional<TaskImageContainer.TaskImage> image,
58                             Optional<CaptureResultProxy> captureResult,
59                             Optional<Location> location) {
60        addExifVersionToExif();
61        addTimestampToExif();
62        addMakeAndModelToExif();
63        if (image.isPresent()) {
64            addImageDataToExif(image.get());
65        }
66        if (captureResult.isPresent()) {
67            addCaptureResultToExif(captureResult.get());
68        }
69        if (location.isPresent()) {
70            addLocationToExif(location.get());
71        }
72    }
73
74    /**
75     * Adds the given location to the EXIF object.
76     *
77     * @param location The location to add.
78     */
79    public void addLocationToExif(Location location) {
80        final Long ALTITUDE_PRECISION = 1L; // GPS altitude isn't particularly accurate (determined empirically)
81
82        mExif.addGpsTags(location.getLatitude(), location.getLongitude());
83        mExif.addGpsDateTimeStampTag(location.getTime());
84
85        if (location.hasAltitude()) {
86            double altitude = location.getAltitude();
87            addExifTag(ExifInterface.TAG_GPS_ALTITUDE, rational(altitude, ALTITUDE_PRECISION));
88            short altitudeRef = altitude < 0 ? ExifInterface.GpsAltitudeRef.SEA_LEVEL_NEGATIVE
89                    : ExifInterface.GpsAltitudeRef.SEA_LEVEL;
90            addExifTag(ExifInterface.TAG_GPS_ALTITUDE_REF, altitudeRef);
91        }
92    }
93
94    private void addExifVersionToExif() {
95        addExifTag(ExifInterface.TAG_EXIF_VERSION, ExifInterface.EXIF_VERSION);
96    }
97
98    private void addTimestampToExif() {
99        final Long MS_TO_S = 1000L; // Milliseconds per second
100        final String subSecondFormat = "000";
101
102        Long timestampMs = System.currentTimeMillis();
103        TimeZone timezone = Calendar.getInstance().getTimeZone();
104        mExif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, timestampMs, timezone);
105        mExif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME_DIGITIZED, timestampMs, timezone);
106        mExif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME_ORIGINAL, timestampMs, timezone);
107
108        Long subSeconds = timestampMs % MS_TO_S;
109        String subSecondsString = new DecimalFormat(subSecondFormat).format(subSeconds);
110        addExifTag(ExifInterface.TAG_SUB_SEC_TIME, subSecondsString);
111        addExifTag(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL, subSecondsString);
112        addExifTag(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED, subSecondsString);
113    }
114
115    private void addMakeAndModelToExif() {
116        addExifTag(ExifInterface.TAG_MAKE, Build.MANUFACTURER);
117        addExifTag(ExifInterface.TAG_MODEL, Build.MODEL);
118    }
119
120    private void addImageDataToExif(TaskImageContainer.TaskImage image) {
121        mExif.setTag(mExif.buildTag(ExifInterface.TAG_PIXEL_X_DIMENSION, image.width));
122        mExif.setTag(mExif.buildTag(ExifInterface.TAG_PIXEL_Y_DIMENSION, image.height));
123        mExif.setTag(mExif.buildTag(ExifInterface.TAG_IMAGE_WIDTH, image.width));
124        mExif.setTag(mExif.buildTag(ExifInterface.TAG_IMAGE_LENGTH, image.height));
125        mExif.setTag(mExif.buildTag(ExifInterface.TAG_ORIENTATION,
126                ExifInterface.getOrientationValueForRotation(image.orientation.getDegrees())));
127    }
128
129    private void addCaptureResultToExif(CaptureResultProxy result) {
130        final Long NS_TO_S = 1000000000L; // Nanoseconds per second
131        final Long SHUTTER_SPEED_VALUE_PRECISION = 1000L;
132        final Long F_NUMBER_PRECISION = 100L;
133        final Long APERTURE_VALUE_PRECISION = 100L;
134        final Long FOCAL_LENGTH_PRECISION = 1000L; // micrometer precision
135
136        // Exposure time
137        Long exposureTimeNs = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
138        addExifTag(ExifInterface.TAG_EXPOSURE_TIME, ratio(exposureTimeNs, NS_TO_S));
139
140        // Shutter speed value (APEX unit, see Jeita EXIF 2.2 spec Annex C).
141        if (exposureTimeNs != null) {
142            Double exposureTime = (double) exposureTimeNs / NS_TO_S;
143            Double shutterSpeedValue = -log2(exposureTime);
144            addExifTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, rational(shutterSpeedValue, SHUTTER_SPEED_VALUE_PRECISION));
145        }
146
147        // ISO
148        addExifTag(ExifInterface.TAG_ISO_SPEED_RATINGS, result.get(CaptureResult.SENSOR_SENSITIVITY));
149
150        // F-stop number
151        Float fNumber = result.get(CaptureResult.LENS_APERTURE);
152        addExifTag(ExifInterface.TAG_F_NUMBER, rational(fNumber, F_NUMBER_PRECISION));
153
154        // Aperture value (APEX unit, see Jeita EXIF 2.2 spec Annex C).
155        if (fNumber != null) {
156            Double apertureValue = 2 * log2(fNumber);
157            addExifTag(ExifInterface.TAG_APERTURE_VALUE, rational(apertureValue, APERTURE_VALUE_PRECISION));
158        }
159
160        // Focal length
161        Float focalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH);
162        addExifTag(ExifInterface.TAG_FOCAL_LENGTH, rational(focalLength, FOCAL_LENGTH_PRECISION));
163
164        // Flash mode
165        Integer flashMode = result.get(CaptureResult.FLASH_MODE);
166        if (flashMode == CaptureResult.FLASH_MODE_OFF) {
167            addExifTag(ExifInterface.TAG_FLASH, ExifInterface.Flash.DID_NOT_FIRE);
168        } else {
169            addExifTag(ExifInterface.TAG_FLASH, ExifInterface.Flash.FIRED);
170        }
171
172        // White balance
173        Integer whiteBalanceMode = result.get(CaptureResult.CONTROL_AWB_MODE);
174        if (whiteBalanceMode == CaptureResult.CONTROL_AWB_MODE_OFF) {
175            addExifTag(ExifInterface.TAG_WHITE_BALANCE, ExifInterface.WhiteBalance.MANUAL);
176        } else {
177            addExifTag(ExifInterface.TAG_WHITE_BALANCE, ExifInterface.WhiteBalance.AUTO);
178        }
179
180    }
181
182    private void addExifTag(int tagId, Object val) {
183        if (val != null) {
184            mExif.setTag(mExif.buildTag(tagId, val));
185        }
186    }
187
188    private Rational ratio(Long numerator, Long denominator) {
189        if (numerator != null && denominator != null) {
190            return new Rational(numerator, denominator);
191        }
192        return null;
193    }
194    private Rational rational(Float value, Long precision) {
195        if (value != null && precision != null) {
196            return new Rational((long) (value * precision), precision);
197        }
198        return null;
199    }
200
201    private Rational rational(Double value, Long precision) {
202        if (value != null && precision != null) {
203            return new Rational((long) (value * precision), precision);
204        }
205        return null;
206    }
207
208    private Double log2(Float value) {
209        if (value != null) {
210            return Math.log(value) / LOG_2;
211        }
212        return null;
213    }
214
215    private Double log2(Double value) {
216        if (value != null) {
217            return Math.log(value) / LOG_2;
218        }
219        return null;
220    }
221}
222