From patchwork Thu Jan 14 10:40:33 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10860 X-Patchwork-Delegate: paul.elder@ideasonboard.com Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id B7989BD808 for ; Thu, 14 Jan 2021 10:40:55 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6F96F680F4; Thu, 14 Jan 2021 11:40:55 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="HlriAdxZ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BB9EC680E2 for ; Thu, 14 Jan 2021 11:40:54 +0100 (CET) Received: from pyrite.rasen.tech (unknown [IPv6:2400:4051:61:600:2c71:1b79:d06d:5032]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 567278D7; Thu, 14 Jan 2021 11:40:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1610620854; bh=zrWLaVR7re63TKBxBOmUkX/284VL9iPXlvYRcV0Iv0M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HlriAdxZLgpNnkc9VAMSKtO+TMNpsqXIAITkDWdl1enwrlUyhF7/jRAn8dG/XIH0n oyPLLh0b/p/hqzm8gxoLcwiaFZsK0F8rc0T64a4ayfTnIa31umknnBuGubT7xYCmMG fkWQJs5ua65mhcb8rzOSCZoTbBvL+TF3YMGW/W2k= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 14 Jan 2021 19:40:33 +0900 Message-Id: <20210114104035.302968-5-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210114104035.302968-1-paul.elder@ideasonboard.com> References: <20210114104035.302968-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 4/6] android: jpeg: exif: Add functions for setting various values X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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 --- 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 +#include + #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(tm.tm_hour), 1 }, + { static_cast(tm.tm_min), 1 }, + { static_cast(tm.tm_sec), 1 }, + }; + + memcpy(entry->data, ts, length); + 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(EXIF_TAG_GPS_TIME_STAMP), tm); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_DATE_STAMP), EXIF_FORMAT_ASCII, ts); +} + +std::tuple 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(deg), 1 }, + { static_cast(min), 1 }, + { static_cast(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(latDeg, latMin, latSec) = degreesToDMS(coords[0]); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE_REF), + EXIF_FORMAT_ASCII, latDeg >= 0 ? "N" : "S"); + setGPSDMS(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE), + std::abs(latDeg), latMin, latSec); + + std::tie(longDeg, longMin, longSec) = degreesToDMS(coords[1]); + setString(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LONGITUDE_REF), + EXIF_FORMAT_ASCII, longDeg >= 0 ? "E" : "W"); + setGPSDMS(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_LATITUDE), + std::abs(longDeg), longMin, longSec); + + setByte(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_ALTITUDE_REF), + coords[2] >= 0 ? 0 : 1); + setRational(EXIF_IFD_GPS, static_cast(EXIF_TAG_GPS_ALTITUDE), + ExifRational{ static_cast(std::abs(coords[2])), 1 }); +} + +void Exif::setGPSMethod(const std::string &method) +{ + std::vector 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(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 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(length), 1 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, rational); +} + +void Exif::setExposureTime(int64_t sec) +{ + ExifRational rational = { static_cast(sec), 1 }; + setRational(EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME, rational); +} + +void Exif::setAperture(float size) +{ + ExifRational rational = { static_cast(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(flash)); +} + +void Exif::setWhiteBalance(WhiteBalance wb) +{ + setShort(EXIF_IFD_EXIF, EXIF_TAG_WHITE_BALANCE, static_cast(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 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 degreesToDMS(double decimalDegrees); + void setGPSDMS(ExifIfd ifd, ExifTag tag, int deg, int min, int sec); bool valid_;