From patchwork Thu Jan 21 10:15:41 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 10913 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 452B5C0F2A for ; Thu, 21 Jan 2021 10:16:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 12594681DB; Thu, 21 Jan 2021 11:16:05 +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="X1OKmaqk"; 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 C5C23681A0 for ; Thu, 21 Jan 2021 11:16:03 +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 3C57550E; Thu, 21 Jan 2021 11:16:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1611224163; bh=VVKWPshAHlzwGDCJA+HhMPLk7FTlPwYWPVztdK4UFIc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=X1OKmaqkVA7fMGW260A/0135OrAq9AsHenW6/JiAnb308j+qc6STuWg8zZiQEEuVF wCvvqOQVI4+5W/B22+n0PZWWUKqp9kSn581/n6oPQ4Oz54jZ8IQQo+qXs/LNpVPDML FMar7BsKlkzv+BwKAhzF7aPshTJpJ5fo5Lhguq1g= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Thu, 21 Jan 2021 19:15:41 +0900 Message-Id: <20210121101549.134574-2-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20210121101549.134574-1-paul.elder@ideasonboard.com> References: <20210121101549.134574-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 1/9] android: jpeg: exif: Expand setString to support different encodings 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" GPSProcessingMethod and UserComment in EXIF tags can be in UTF-16. Expand setString to take an encoding when the field type is undefined. Update callers accordingly. Signed-off-by: Paul Elder --- Changes in v2: - moved from utils into exif - support no-encoding --- src/android/jpeg/exif.cpp | 107 +++++++++++++++++++++++++++++++------- src/android/jpeg/exif.h | 12 ++++- 2 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 33b3fa7f..cff366a4 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" @@ -64,7 +67,7 @@ Exif::Exif() exif_data_set_byte_order(data_, order_); setString(EXIF_IFD_EXIF, EXIF_TAG_EXIF_VERSION, - EXIF_FORMAT_UNDEFINED, "0231"); + EXIF_FORMAT_UNDEFINED, NoEncoding, "0231"); /* Create the mandatory EXIF fields with default data. */ exif_data_fix(data_); @@ -178,10 +181,18 @@ void Exif::setRational(ExifIfd ifd, ExifTag tag, ExifRational item) exif_entry_unref(entry); } -void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item) +static const std::map> StringEncodingCodes = { + { Exif::ASCII, { 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 } }, + { Exif::JIS, { 0x4a, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 } }, + { Exif::Unicode, { 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, 0x00 } }, + { Exif::Undefined, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }, +}; + +void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, + StringEncoding enc, const std::string &item) { std::string ascii; - size_t length; + size_t length = 0; const char *str; if (format == EXIF_FORMAT_ASCII) { @@ -190,14 +201,44 @@ void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::str /* Pad 1 extra byte to null-terminate the ASCII string. */ length = ascii.length() + 1; - } else { - str = item.c_str(); - - /* - * Strings stored in different formats (EXIF_FORMAT_UNDEFINED) - * are not null-terminated. - */ - length = item.length(); + } else if (format == EXIF_FORMAT_UNDEFINED) { + std::vector buf; + const std::vector &constbuf = buf; + std::u16string u16str; + + switch (enc) { + case Unicode: + buf = StringEncodingCodes.find(enc)->second; + u16str = utf8ToUtf16(item); + + /* Little-endian */ + buf.resize(8 + u16str.size() * 2); + for (size_t i = 0; i < u16str.size(); i++) { + buf[8 + 2 * i] = (u16str[i] & 0xff); + buf[8 + 2 * i + 1] = ((u16str[i] >> 8) & 0xff); + } + + str = reinterpret_cast(constbuf.data()); + length = buf.size(); + + break; + /* \todo Support JIS */ + case JIS: + case ASCII: + case Undefined: + buf = StringEncodingCodes.find(enc)->second; + [[fallthrough]]; + default: + buf.insert(buf.end(), item.begin(), item.end()); + str = reinterpret_cast(constbuf.data()); + + /* + * Strings stored in different formats (EXIF_FORMAT_UNDEFINED) + * are not null-terminated. + */ + length = buf.size(); + break; + } } ExifEntry *entry = createEntry(ifd, tag, format, length, length); @@ -210,12 +251,12 @@ void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::str void Exif::setMake(const std::string &make) { - setString(EXIF_IFD_0, EXIF_TAG_MAKE, EXIF_FORMAT_ASCII, make); + setString(EXIF_IFD_0, EXIF_TAG_MAKE, EXIF_FORMAT_ASCII, NoEncoding, make); } void Exif::setModel(const std::string &model) { - setString(EXIF_IFD_0, EXIF_TAG_MODEL, EXIF_FORMAT_ASCII, model); + setString(EXIF_IFD_0, EXIF_TAG_MODEL, EXIF_FORMAT_ASCII, NoEncoding, model); } void Exif::setSize(const Size &size) @@ -233,9 +274,9 @@ void Exif::setTimestamp(time_t timestamp) strftime(str, sizeof(str), "%Y:%m:%d %H:%M:%S", &tm); std::string ts(str); - setString(EXIF_IFD_0, EXIF_TAG_DATE_TIME, EXIF_FORMAT_ASCII, ts); - setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_FORMAT_ASCII, ts); - setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED, EXIF_FORMAT_ASCII, ts); + setString(EXIF_IFD_0, EXIF_TAG_DATE_TIME, EXIF_FORMAT_ASCII, NoEncoding, ts); + setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_FORMAT_ASCII, NoEncoding, ts); + setString(EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED, EXIF_FORMAT_ASCII, NoEncoding, ts); /* Query and set timezone information if available. */ int r = strftime(str, sizeof(str), "%z", &tm); @@ -244,13 +285,13 @@ void Exif::setTimestamp(time_t timestamp) tz.insert(3, 1, ':'); setString(EXIF_IFD_EXIF, static_cast(_ExifTag::OFFSET_TIME), - EXIF_FORMAT_ASCII, tz); + EXIF_FORMAT_ASCII, NoEncoding, tz); setString(EXIF_IFD_EXIF, static_cast(_ExifTag::OFFSET_TIME_ORIGINAL), - EXIF_FORMAT_ASCII, tz); + EXIF_FORMAT_ASCII, NoEncoding, tz); setString(EXIF_IFD_EXIF, static_cast(_ExifTag::OFFSET_TIME_DIGITIZED), - EXIF_FORMAT_ASCII, tz); + EXIF_FORMAT_ASCII, NoEncoding, tz); } } @@ -290,6 +331,34 @@ void Exif::setThumbnail(Span thumbnail, setShort(EXIF_IFD_0, EXIF_TAG_COMPRESSION, compression); } +/** + * \brief Convert UTF-8 string to UTF-16 string + * \param[in] str String to convert + * + * \return \a str in UTF-16 + */ +std::u16string Exif::utf8ToUtf16(const std::string &str) +{ + std::mbstate_t state{}; + char16_t c16; + const char *ptr = str.data(); + const char *end = ptr + str.size(); + + std::u16string ret; + while (size_t rc = mbrtoc16(&c16, ptr, end - ptr + 1, &state)) { + if (rc == static_cast(-2) || + rc == static_cast(-1)) + break; + + ret.push_back(c16); + + if (rc > 0) + ptr += rc; + } + + return ret; +} + [[nodiscard]] int Exif::generate() { if (exifData_) { diff --git a/src/android/jpeg/exif.h b/src/android/jpeg/exif.h index 5cab4559..db98ba63 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -26,6 +26,14 @@ public: JPEG = 6, }; + enum StringEncoding { + NoEncoding = 0, + ASCII = 1, + JIS = 2, + Unicode = 3, + Undefined = 4, + }; + void setMake(const std::string &make); void setModel(const std::string &model); @@ -46,9 +54,11 @@ private: 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); + StringEncoding enc, const std::string &item); void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); + std::u16string utf8ToUtf16(const std::string &str); + bool valid_; ExifData *data_;