[libcamera-devel] [PATCH 4/6] android: jpeg: exif: Add functions for setting various values

Paul Elder paul.elder at ideasonboard.com
Thu Jan 14 11:40:33 CET 2021


Add functions for setting the following EXIF fields:

- GPSDatestamp
- GPSTimestamp
- GPSLocation
  - GPSLatitudeRef
  - GPSLatitude
  - GPSLongitudeRef
  - GPSLongitude
  - GPSAltitudeRef
  - GPSAltitude
- GPSProcessingMethod
- FocalLength
- ExposureTime
- FNumber
- ISO
- Flash
- WhiteBalance
- SubsecTime
- SubsecTimeOriginal
- SubsecTimeDigitized

These are in preparation for fixing the following CTS tests:

- android.hardware.camera2.cts.StillCaptureTest#testFocalLengths
- android.hardware.camera2.cts.StillCaptureTest#testJpegExif

Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
---
 src/android/jpeg/exif.cpp | 187 ++++++++++++++++++++++++++++++++++++++
 src/android/jpeg/exif.h   |  41 +++++++++
 2 files changed, 228 insertions(+)

diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp
index b19cb4cd..fde07a63 100644
--- a/src/android/jpeg/exif.cpp
+++ b/src/android/jpeg/exif.cpp
@@ -7,6 +7,9 @@
 
 #include "exif.h"
 
+#include <cmath>
+#include <tuple>
+
 #include "libcamera/internal/log.h"
 #include "libcamera/internal/utils.h"
 
@@ -148,6 +151,16 @@ ExifEntry *Exif::createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format,
 	return entry;
 }
 
+void Exif::setByte(ExifIfd ifd, ExifTag tag, uint8_t item)
+{
+	ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_BYTE, 1, 1);
+	if (!entry)
+		return;
+
+	memcpy(entry->data, &item, 1);
+	exif_entry_unref(entry);
+}
+
 void Exif::setShort(ExifIfd ifd, ExifTag tag, uint16_t item)
 {
 	ExifEntry *entry = createEntry(ifd, tag);
@@ -178,6 +191,22 @@ void Exif::setRational(ExifIfd ifd, ExifTag tag, ExifRational item)
 	exif_entry_unref(entry);
 }
 
+/*
+ * \brief setArray
+ * \param[in] size sizeof(data[0])
+ * \param[in] count Number of elements in data
+ */
+void Exif::setArray(ExifIfd ifd, ExifTag tag, ExifFormat format,
+		    const void *data, size_t size, size_t count)
+{
+	ExifEntry *entry = createEntry(ifd, tag, format, count, size * count);
+	if (!entry)
+		return;
+
+	memcpy(entry->data, data, size * count);
+	exif_entry_unref(entry);
+}
+
 void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item)
 {
 	std::string ascii;
@@ -254,6 +283,111 @@ void Exif::setTimestamp(time_t timestamp)
 	}
 }
 
+void Exif::setGPSTimestamp(ExifIfd ifd, ExifTag tag, const struct tm &tm)
+{
+	size_t length = 3 * sizeof(ExifRational);
+
+	ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, length);
+	if (!entry)
+		return;
+
+	ExifRational ts[] = {
+		{ static_cast<ExifLong>(tm.tm_hour), 1 },
+		{ static_cast<ExifLong>(tm.tm_min),  1 },
+		{ static_cast<ExifLong>(tm.tm_sec),  1 },
+	};
+
+	memcpy(entry->data, ts, length);
+	exif_entry_unref(entry);
+}
+
+void Exif::setGPSDateTimestamp(time_t timestamp)
+{
+	struct tm tm;
+	gmtime_r(&timestamp, &tm);
+
+	char str[11];
+	strftime(str, sizeof(str), "%Y:%m:%d", &tm);
+	std::string ts(str);
+
+	setGPSTimestamp(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_TIME_STAMP), tm);
+	setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_DATE_STAMP), EXIF_FORMAT_ASCII, ts);
+}
+
+std::tuple<int, int, int> Exif::degreesToDMS(double decimalDegrees)
+{
+	int degrees = std::trunc(decimalDegrees);
+	double minutes = std::abs((decimalDegrees - degrees) * 60);
+	double seconds = (minutes - std::trunc(minutes)) * 60;
+
+	return { degrees, std::trunc(minutes), std::round(seconds) };
+}
+
+void Exif::setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec)
+{
+	size_t length = 3 * sizeof(ExifRational);
+
+	ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, length);
+	if (!entry)
+		return;
+
+	ExifRational coords[] = {
+		{ static_cast<ExifLong>(deg), 1 },
+		{ static_cast<ExifLong>(min), 1 },
+		{ static_cast<ExifLong>(sec), 1 },
+	};
+
+	memcpy(entry->data, coords, length);
+	exif_entry_unref(entry);
+}
+
+/*
+ * \brief Set GPS location (lat, long, alt) from Android JPEG GPS coordinates
+ * \param[in] coords Pointer to coordinates latitude, longitude, and altitude,
+ *            first two in degrees, the third in meters
+ */
+void Exif::setGPSLocation(const double *coords)
+{
+	int latDeg, latMin, latSec, longDeg, longMin, longSec;
+
+	std::tie<int, int, int>(latDeg, latMin, latSec) = degreesToDMS(coords[0]);
+	setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE_REF),
+		  EXIF_FORMAT_ASCII, latDeg >= 0 ? "N" : "S");
+	setGPSDMS(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE),
+		  std::abs(latDeg), latMin, latSec);
+
+	std::tie<int, int, int>(longDeg, longMin, longSec) = degreesToDMS(coords[1]);
+	setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LONGITUDE_REF),
+		  EXIF_FORMAT_ASCII, longDeg >= 0 ? "E" : "W");
+	setGPSDMS(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE),
+		  std::abs(longDeg), longMin, longSec);
+
+	setByte(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_ALTITUDE_REF),
+		coords[2] >= 0 ? 0 : 1);
+	setRational(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_ALTITUDE),
+		    ExifRational{ static_cast<ExifLong>(std::abs(coords[2])), 1 });
+}
+
+void Exif::setGPSMethod(const std::string &method)
+{
+	std::vector<uint8_t> buf = libcamera::utils::string_to_c16(method, true);
+	/* Designate that this string is Unicode (UCS-2) */
+	buf.insert(buf.begin(), { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 });
+
+	/* 8 bytes for character code designation, plus 32 bytes from android */
+	unsigned int nullTerm = 39;
+	for (int i = 8; i < buf.size(); i++) {
+		if (!buf[i]) {
+			nullTerm = i;
+			break;
+		}
+	}
+	buf.resize(nullTerm);
+
+	setArray(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_PROCESSING_METHOD),
+		 EXIF_FORMAT_UNDEFINED, buf.data(), 1, buf.size());
+}
+
 void Exif::setOrientation(int orientation)
 {
 	int value;
@@ -288,6 +422,59 @@ void Exif::setThumbnail(Span<const unsigned char> thumbnail,
 	data_->size = thumbnail.size();
 
 	setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression);
+	setLong(EXIF_IFD_0, EXIF_TAG_JPEG_INTERCHANGE_FORMAT, 0);
+	setLong(EXIF_IFD_0, EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, data_->size);
+}
+
+void Exif::setFocalLength(float length)
+{
+	ExifRational rational = { static_cast<ExifLong>(length), 1 };
+	setRational(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, rational);
+}
+
+void Exif::setExposureTime(int64_t sec)
+{
+	ExifRational rational = { static_cast<ExifLong>(sec), 1 };
+	setRational(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, rational);
+}
+
+void Exif::setAperture(float size)
+{
+	ExifRational rational = { static_cast<ExifLong>(size * 10000), 10000 };
+	setRational(EXIF_IFD_EXIF, EXIF_TAG_FNUMBER, rational);
+}
+
+void Exif::setISO(int16_t iso)
+{
+	setShort(EXIF_IFD_EXIF, EXIF_TAG_ISO_SPEED_RATINGS, iso);
+}
+
+void Exif::setFlash(Flash flash)
+{
+	setShort(EXIF_IFD_EXIF, EXIF_TAG_FLASH, static_cast<ExifShort>(flash));
+}
+
+void Exif::setWhiteBalance(WhiteBalance wb)
+{
+	setShort(EXIF_IFD_EXIF, EXIF_TAG_WHITE_BALANCE, static_cast<ExifShort>(wb));
+}
+
+void Exif::setSubsecTime(uint64_t subsec)
+{
+	setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME,
+		  EXIF_FORMAT_ASCII, std::to_string(subsec));
+}
+
+void Exif::setSubsecTimeOriginal(uint64_t subsec)
+{
+	setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL,
+		  EXIF_FORMAT_ASCII, std::to_string(subsec));
+}
+
+void Exif::setSubsecTimeDigitized(uint64_t subsec)
+{
+	setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED,
+		  EXIF_FORMAT_ASCII, std::to_string(subsec));
 }
 
 [[nodiscard]] int Exif::generate()
diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h
index 5cab4559..cd3b78cd 100644
--- a/src/android/jpeg/exif.h
+++ b/src/android/jpeg/exif.h
@@ -26,6 +26,28 @@ public:
 		JPEG = 6,
 	};
 
+	enum Flash {
+		/* bit 0 */
+		Fired = 0x01,
+		/* bits 1 and 2 */
+		StrobeReserved = 0x02,
+		StrobeDetected = 0x04,
+		StrobeNotDetected = 0x06,
+		/* bits 3 and 4 */
+		ModeCompulsoryFiring = 0x08,
+		ModeCompulsorySuppression = 0x10,
+		ModeAuto = 0x18,
+		/* bit 5 */
+		FlashNotPresent = 0x20,
+		/* bit 6 */
+		RedEye = 0x40,
+	};
+
+	enum WhiteBalance {
+		Auto = 0,
+		Manual = 1,
+	};
+
 	void setMake(const std::string &make);
 	void setModel(const std::string &model);
 
@@ -35,6 +57,19 @@ public:
 			  Compression compression);
 	void setTimestamp(time_t timestamp);
 
+	void setGPSDateTimestamp(time_t timestamp);
+	void setGPSLocation(const double *coords);
+	void setGPSMethod(const std::string &method);
+	void setFocalLength(float length);
+	void setExposureTime(int64_t sec);
+	void setAperture(float size);
+	void setISO(int16_t iso);
+	void setFlash(Flash flash);
+	void setWhiteBalance(WhiteBalance wb);
+	void setSubsecTime(uint64_t subsec);
+	void setSubsecTimeOriginal(uint64_t subsec);
+	void setSubsecTimeDigitized(uint64_t subsec);
+
 	libcamera::Span<const uint8_t> data() const { return { exifData_, size_ }; }
 	[[nodiscard]] int generate();
 
@@ -43,11 +78,17 @@ private:
 	ExifEntry *createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format,
 			       unsigned long components, unsigned int size);
 
+	void setArray(ExifIfd ifd, ExifTag tag, ExifFormat format,
+		      const void *data, size_t size, size_t count);
+	void setByte(ExifIfd ifd, ExifTag tag, uint8_t item);
 	void setShort(ExifIfd ifd, ExifTag tag, uint16_t item);
 	void setLong(ExifIfd ifd, ExifTag tag, uint32_t item);
 	void setString(ExifIfd ifd, ExifTag tag, ExifFormat format,
 		       const std::string &item);
 	void setRational(ExifIfd ifd, ExifTag tag, ExifRational item);
+	void setGPSTimestamp(ExifIfd ifd, ExifTag tag, const struct tm &tm);
+	std::tuple<int, int, int> degreesToDMS(double decimalDegrees);
+	void setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec);
 
 	bool valid_;
 
-- 
2.27.0



More information about the libcamera-devel mailing list