[libcamera-devel] [PATCH 4/6] android: jpeg: exif: Add functions for setting various values
Jacopo Mondi
jacopo at jmondi.org
Fri Jan 15 16:00:52 CET 2021
Hi Paul,
On Thu, Jan 14, 2021 at 07:40:33PM +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
>
Awesome!
> 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);
I know nothing about the exif library, but I see
exif_set_[short|long|rational] etc.. Isn't there an
exif_set_[byte|char] ? If it's not there, there might be a reason why
it has been left out ?
> + 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
Very brief!
I think you can omit this doc block or rather expand it.
> + * \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 },
> + };
Is this required to be rational ? the '1' here I assume is the
denominator..
> +
> + memcpy(entry->data, ts, length);
Shouldn't this be setRational() ? Or is this to avoid calling it 3
times ?
> + exif_entry_unref(entry);
> +}
> +
> +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 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) };
> +}
I assume CTS validates the above calculations!
> +
> +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
Where do we expect the GPS coordinates to come from ?
> + * \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 });
> +
This will probable relocate, but it's not that bad I think
> + /* 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);
Was I wrong assuming string_to_c16 stops at \0 ?
> +
> + 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));
Exif really requires a lot of information :/
Good job overall!
> }
>
> [[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
>
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel at lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
More information about the libcamera-devel
mailing list