Message ID | 20210126102825.147026-4-paul.elder@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
Hi Paul, Thank you for the patch. On Tue, Jan 26, 2021 at 07:28:20PM +0900, Paul Elder wrote: > 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@ideasonboard.com> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > Reviewed-by: Jacopo Mondi <jacopo@jmondi.org> > > --- > Changes in v5: > - fix number alignment in string in setting subsec values > - use ms operator from chrono for setting timestamp subsec > - other cosmetic changes > > Changes in v4: > - change type of subsec to std::chrono::milliseconds > > Changes in v3: > - merge setting subsec into setting the main timestamp > > Changes in v2: > - some cosmetic and precision changes > - use the new setString > --- > src/android/jpeg/exif.cpp | 181 +++++++++++++++++++++-- > src/android/jpeg/exif.h | 39 ++++- > src/android/jpeg/post_processor_jpeg.cpp | 5 +- > 3 files changed, 209 insertions(+), 16 deletions(-) > > diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp > index 204a237a..b4b3c0b7 100644 > --- a/src/android/jpeg/exif.cpp > +++ b/src/android/jpeg/exif.cpp > @@ -7,7 +7,11 @@ > > #include "exif.h" > > +#include <cmath> > +#include <iomanip> > #include <map> > +#include <sstream> > +#include <tuple> > #include <uchar.h> > > #include "libcamera/internal/log.h" > @@ -151,6 +155,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; > + > + entry->data[0] = item; > + exif_entry_unref(entry); > +} > + > void Exif::setShort(ExifIfd ifd, ExifTag tag, uint16_t item) > { > ExifEntry *entry = createEntry(ifd, tag); > @@ -267,7 +281,7 @@ void Exif::setSize(const Size &size) > setLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION, size.width); > } > > -void Exif::setTimestamp(time_t timestamp) > +void Exif::setTimestamp(time_t timestamp, std::chrono::milliseconds msec) > { > struct tm tm; > localtime_r(×tamp, &tm); > @@ -282,19 +296,125 @@ void Exif::setTimestamp(time_t timestamp) > > /* Query and set timezone information if available. */ > int r = strftime(str, sizeof(str), "%z", &tm); > - if (r > 0) { > - std::string tz(str); > - tz.insert(3, 1, ':'); > - setString(EXIF_IFD_EXIF, > - static_cast<ExifTag>(_ExifTag::OFFSET_TIME), > - EXIF_FORMAT_ASCII, tz); > - setString(EXIF_IFD_EXIF, > - static_cast<ExifTag>(_ExifTag::OFFSET_TIME_ORIGINAL), > - EXIF_FORMAT_ASCII, tz); > - setString(EXIF_IFD_EXIF, > - static_cast<ExifTag>(_ExifTag::OFFSET_TIME_DIGITIZED), > - EXIF_FORMAT_ASCII, tz); > - } > + if (r <= 0) > + return; > + > + std::string tz(str); > + tz.insert(3, 1, ':'); > + setString(EXIF_IFD_EXIF, > + static_cast<ExifTag>(_ExifTag::OFFSET_TIME), > + EXIF_FORMAT_ASCII, tz); > + setString(EXIF_IFD_EXIF, > + static_cast<ExifTag>(_ExifTag::OFFSET_TIME_ORIGINAL), > + EXIF_FORMAT_ASCII, tz); > + setString(EXIF_IFD_EXIF, > + static_cast<ExifTag>(_ExifTag::OFFSET_TIME_DIGITIZED), > + EXIF_FORMAT_ASCII, tz); > + > + std::stringstream subsec; > + subsec << std::setfill('0') << std::setw(3) << std::to_string(msec.count()); Shouldn't you drop std::to_string() ? subsec << std::setfill('0') << std::setw(3) << msec.count(); You could also catch the string instead of calling str() thrice. std::stringstream sstr; sstr << std::setfill('0') << std::setw(3) << msec.count(); std::string subsec = subsec.str(); > + > + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME, > + EXIF_FORMAT_ASCII, subsec.str()); > + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, > + EXIF_FORMAT_ASCII, subsec.str()); > + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED, > + EXIF_FORMAT_ASCII, subsec.str()); > +} > + > +void Exif::setGPSDateTimestamp(time_t timestamp) > +{ > + struct tm tm; > + gmtime_r(×tamp, &tm); > + > + char str[11]; > + strftime(str, sizeof(str), "%Y:%m:%d", &tm); > + std::string tsStr(str); > + > + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_DATE_STAMP), > + EXIF_FORMAT_ASCII, tsStr); > + > + /* Set GPS_TIME_STAMP */ > + ExifEntry *entry = > + createEntry(EXIF_IFD_GPS, > + static_cast<ExifTag>(EXIF_TAG_GPS_TIME_STAMP), > + EXIF_FORMAT_RATIONAL, 3, 3 * sizeof(ExifRational)); > + 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 }, > + }; > + > + for (int i = 0; i < 3; i++) > + exif_set_rational(entry->data + i * sizeof(ExifRational), > + order_, ts[i]); > + > + exif_entry_unref(entry); > +} > + > +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) > +{ > + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, > + 3 * sizeof(ExifRational)); > + if (!entry) > + return; > + > + ExifRational coords[] = { > + { static_cast<ExifLong>(deg), 1 }, > + { static_cast<ExifLong>(min), 1 }, > + { static_cast<ExifLong>(sec), 1 }, > + }; > + > + for (int i = 0; i < 3; i++) > + exif_set_rational(entry->data + i * sizeof(ExifRational), > + order_, coords[i]); > + > + exif_entry_unref(entry); > +} > + > +/* > + * \brief Set GPS location (lat, long, alt) > + * \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 deg, min, sec; > + > + std::tie<int, int, int>(deg, min, sec) = degreesToDMS(coords[0]); > + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE_REF), > + EXIF_FORMAT_ASCII, deg >= 0 ? "N" : "S"); > + setGPSDMS(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE), > + std::abs(deg), min, sec); > + > + std::tie<int, int, int>(deg, min, sec) = degreesToDMS(coords[1]); > + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LONGITUDE_REF), > + EXIF_FORMAT_ASCII, deg >= 0 ? "E" : "W"); > + setGPSDMS(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE), > + std::abs(deg), min, sec); > + > + 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) > +{ > + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_PROCESSING_METHOD), > + EXIF_FORMAT_UNDEFINED, method, Unicode); > } > > void Exif::setOrientation(int orientation) > @@ -333,6 +453,39 @@ void Exif::setThumbnail(Span<const unsigned char> thumbnail, > setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); > } > > +void Exif::setFocalLength(float length) > +{ > + ExifRational rational = { static_cast<ExifLong>(length * 1000), 1000 }; > + setRational(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, rational); > +} > + > +void Exif::setExposureTime(uint64_t nsec) > +{ > + ExifRational rational = { static_cast<ExifLong>(nsec), 1000000000 }; > + 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(uint16_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)); > +} > + > /** > * \brief Convert UTF-8 string to UTF-16 string > * \param[in] str String to convert > diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h > index 8b84165b..b0d61402 100644 > --- a/src/android/jpeg/exif.h > +++ b/src/android/jpeg/exif.h > @@ -7,6 +7,7 @@ > #ifndef __ANDROID_JPEG_EXIF_H__ > #define __ANDROID_JPEG_EXIF_H__ > > +#include <chrono> > #include <string> > #include <time.h> > > @@ -26,6 +27,27 @@ public: > JPEG = 6, > }; > > + enum Flash { > + /* bit 0 */ > + Fired = 0x01, > + /* bits 1 and 2 */ > + 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, > + }; > + > enum StringEncoding { > NoEncoding = 0, > ASCII = 1, > @@ -39,7 +61,18 @@ public: > void setSize(const libcamera::Size &size); > void setThumbnail(libcamera::Span<const unsigned char> thumbnail, > Compression compression); > - void setTimestamp(time_t timestamp); > + void setTimestamp(time_t timestamp, std::chrono::milliseconds msec); > + > + void setGPSDateTimestamp(time_t timestamp); > + void setGPSLocation(const double *coords); > + void setGPSMethod(const std::string &method); > + > + void setFocalLength(float length); > + void setExposureTime(uint64_t nsec); > + void setAperture(float size); > + void setISO(uint16_t iso); > + void setFlash(Flash flash); > + void setWhiteBalance(WhiteBalance wb); > > libcamera::Span<const uint8_t> data() const { return { exifData_, size_ }; } > [[nodiscard]] int generate(); > @@ -49,6 +82,7 @@ private: > ExifEntry *createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, > unsigned long components, unsigned int size); > > + 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, > @@ -56,6 +90,9 @@ private: > StringEncoding encoding = NoEncoding); > void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); > > + std::tuple<int, int, int> degreesToDMS(double decimalDegrees); > + void setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec); > + > std::u16string utf8ToUtf16(const std::string &str); > > bool valid_; > diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp > index 436a50f8..aa0f783d 100644 > --- a/src/android/jpeg/post_processor_jpeg.cpp > +++ b/src/android/jpeg/post_processor_jpeg.cpp > @@ -7,6 +7,8 @@ > > #include "post_processor_jpeg.h" > > +#include <chrono> > + > #include "../camera_device.h" > #include "../camera_metadata.h" > #include "encoder_libjpeg.h" > @@ -17,6 +19,7 @@ > #include "libcamera/internal/log.h" > > using namespace libcamera; > +using namespace std::chrono_literals; > > LOG_DEFINE_CATEGORY(JPEG) > > @@ -97,7 +100,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source, > * Since the precision we need for EXIF timestamp is only one > * second, it is good enough. > */ > - exif.setTimestamp(std::time(nullptr)); > + exif.setTimestamp(std::time(nullptr), 0ms); > > std::vector<unsigned char> thumbnail; > generateThumbnail(source, &thumbnail);
diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 204a237a..b4b3c0b7 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -7,7 +7,11 @@ #include "exif.h" +#include <cmath> +#include <iomanip> #include <map> +#include <sstream> +#include <tuple> #include <uchar.h> #include "libcamera/internal/log.h" @@ -151,6 +155,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; + + entry->data[0] = item; + exif_entry_unref(entry); +} + void Exif::setShort(ExifIfd ifd, ExifTag tag, uint16_t item) { ExifEntry *entry = createEntry(ifd, tag); @@ -267,7 +281,7 @@ void Exif::setSize(const Size &size) setLong(EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION, size.width); } -void Exif::setTimestamp(time_t timestamp) +void Exif::setTimestamp(time_t timestamp, std::chrono::milliseconds msec) { struct tm tm; localtime_r(×tamp, &tm); @@ -282,19 +296,125 @@ void Exif::setTimestamp(time_t timestamp) /* Query and set timezone information if available. */ int r = strftime(str, sizeof(str), "%z", &tm); - if (r > 0) { - std::string tz(str); - tz.insert(3, 1, ':'); - setString(EXIF_IFD_EXIF, - static_cast<ExifTag>(_ExifTag::OFFSET_TIME), - EXIF_FORMAT_ASCII, tz); - setString(EXIF_IFD_EXIF, - static_cast<ExifTag>(_ExifTag::OFFSET_TIME_ORIGINAL), - EXIF_FORMAT_ASCII, tz); - setString(EXIF_IFD_EXIF, - static_cast<ExifTag>(_ExifTag::OFFSET_TIME_DIGITIZED), - EXIF_FORMAT_ASCII, tz); - } + if (r <= 0) + return; + + std::string tz(str); + tz.insert(3, 1, ':'); + setString(EXIF_IFD_EXIF, + static_cast<ExifTag>(_ExifTag::OFFSET_TIME), + EXIF_FORMAT_ASCII, tz); + setString(EXIF_IFD_EXIF, + static_cast<ExifTag>(_ExifTag::OFFSET_TIME_ORIGINAL), + EXIF_FORMAT_ASCII, tz); + setString(EXIF_IFD_EXIF, + static_cast<ExifTag>(_ExifTag::OFFSET_TIME_DIGITIZED), + EXIF_FORMAT_ASCII, tz); + + std::stringstream subsec; + subsec << std::setfill('0') << std::setw(3) << std::to_string(msec.count()); + + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME, + EXIF_FORMAT_ASCII, subsec.str()); + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, + EXIF_FORMAT_ASCII, subsec.str()); + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED, + EXIF_FORMAT_ASCII, subsec.str()); +} + +void Exif::setGPSDateTimestamp(time_t timestamp) +{ + struct tm tm; + gmtime_r(×tamp, &tm); + + char str[11]; + strftime(str, sizeof(str), "%Y:%m:%d", &tm); + std::string tsStr(str); + + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_DATE_STAMP), + EXIF_FORMAT_ASCII, tsStr); + + /* Set GPS_TIME_STAMP */ + ExifEntry *entry = + createEntry(EXIF_IFD_GPS, + static_cast<ExifTag>(EXIF_TAG_GPS_TIME_STAMP), + EXIF_FORMAT_RATIONAL, 3, 3 * sizeof(ExifRational)); + 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 }, + }; + + for (int i = 0; i < 3; i++) + exif_set_rational(entry->data + i * sizeof(ExifRational), + order_, ts[i]); + + exif_entry_unref(entry); +} + +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) +{ + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, 3, + 3 * sizeof(ExifRational)); + if (!entry) + return; + + ExifRational coords[] = { + { static_cast<ExifLong>(deg), 1 }, + { static_cast<ExifLong>(min), 1 }, + { static_cast<ExifLong>(sec), 1 }, + }; + + for (int i = 0; i < 3; i++) + exif_set_rational(entry->data + i * sizeof(ExifRational), + order_, coords[i]); + + exif_entry_unref(entry); +} + +/* + * \brief Set GPS location (lat, long, alt) + * \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 deg, min, sec; + + std::tie<int, int, int>(deg, min, sec) = degreesToDMS(coords[0]); + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE_REF), + EXIF_FORMAT_ASCII, deg >= 0 ? "N" : "S"); + setGPSDMS(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE), + std::abs(deg), min, sec); + + std::tie<int, int, int>(deg, min, sec) = degreesToDMS(coords[1]); + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LONGITUDE_REF), + EXIF_FORMAT_ASCII, deg >= 0 ? "E" : "W"); + setGPSDMS(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_LATITUDE), + std::abs(deg), min, sec); + + 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) +{ + setString(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_PROCESSING_METHOD), + EXIF_FORMAT_UNDEFINED, method, Unicode); } void Exif::setOrientation(int orientation) @@ -333,6 +453,39 @@ void Exif::setThumbnail(Span<const unsigned char> thumbnail, setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); } +void Exif::setFocalLength(float length) +{ + ExifRational rational = { static_cast<ExifLong>(length * 1000), 1000 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, rational); +} + +void Exif::setExposureTime(uint64_t nsec) +{ + ExifRational rational = { static_cast<ExifLong>(nsec), 1000000000 }; + 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(uint16_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)); +} + /** * \brief Convert UTF-8 string to UTF-16 string * \param[in] str String to convert diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index 8b84165b..b0d61402 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -7,6 +7,7 @@ #ifndef __ANDROID_JPEG_EXIF_H__ #define __ANDROID_JPEG_EXIF_H__ +#include <chrono> #include <string> #include <time.h> @@ -26,6 +27,27 @@ public: JPEG = 6, }; + enum Flash { + /* bit 0 */ + Fired = 0x01, + /* bits 1 and 2 */ + 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, + }; + enum StringEncoding { NoEncoding = 0, ASCII = 1, @@ -39,7 +61,18 @@ public: void setSize(const libcamera::Size &size); void setThumbnail(libcamera::Span<const unsigned char> thumbnail, Compression compression); - void setTimestamp(time_t timestamp); + void setTimestamp(time_t timestamp, std::chrono::milliseconds msec); + + void setGPSDateTimestamp(time_t timestamp); + void setGPSLocation(const double *coords); + void setGPSMethod(const std::string &method); + + void setFocalLength(float length); + void setExposureTime(uint64_t nsec); + void setAperture(float size); + void setISO(uint16_t iso); + void setFlash(Flash flash); + void setWhiteBalance(WhiteBalance wb); libcamera::Span<const uint8_t> data() const { return { exifData_, size_ }; } [[nodiscard]] int generate(); @@ -49,6 +82,7 @@ private: ExifEntry *createEntry(ExifIfd ifd, ExifTag tag, ExifFormat format, unsigned long components, unsigned int size); + 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, @@ -56,6 +90,9 @@ private: StringEncoding encoding = NoEncoding); void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); + std::tuple<int, int, int> degreesToDMS(double decimalDegrees); + void setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec); + std::u16string utf8ToUtf16(const std::string &str); bool valid_; diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp index 436a50f8..aa0f783d 100644 --- a/src/android/jpeg/post_processor_jpeg.cpp +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -7,6 +7,8 @@ #include "post_processor_jpeg.h" +#include <chrono> + #include "../camera_device.h" #include "../camera_metadata.h" #include "encoder_libjpeg.h" @@ -17,6 +19,7 @@ #include "libcamera/internal/log.h" using namespace libcamera; +using namespace std::chrono_literals; LOG_DEFINE_CATEGORY(JPEG) @@ -97,7 +100,7 @@ int PostProcessorJpeg::process(const FrameBuffer &source, * Since the precision we need for EXIF timestamp is only one * second, it is good enough. */ - exif.setTimestamp(std::time(nullptr)); + exif.setTimestamp(std::time(nullptr), 0ms); std::vector<unsigned char> thumbnail; generateThumbnail(source, &thumbnail);