diff options
Diffstat (limited to 'src/android/jpeg')
-rw-r--r-- | src/android/jpeg/encoder.h | 16 | ||||
-rw-r--r-- | src/android/jpeg/encoder_libjpeg.cpp | 36 | ||||
-rw-r--r-- | src/android/jpeg/encoder_libjpeg.h | 21 | ||||
-rw-r--r-- | src/android/jpeg/exif.cpp | 315 | ||||
-rw-r--r-- | src/android/jpeg/exif.h | 65 | ||||
-rw-r--r-- | src/android/jpeg/post_processor_jpeg.cpp | 196 | ||||
-rw-r--r-- | src/android/jpeg/post_processor_jpeg.h | 45 | ||||
-rw-r--r-- | src/android/jpeg/thumbnailer.cpp | 93 | ||||
-rw-r--r-- | src/android/jpeg/thumbnailer.h | 34 |
9 files changed, 756 insertions, 65 deletions
diff --git a/src/android/jpeg/encoder.h b/src/android/jpeg/encoder.h index cf26d67a..a28522f4 100644 --- a/src/android/jpeg/encoder.h +++ b/src/android/jpeg/encoder.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * @@ -7,19 +7,21 @@ #ifndef __ANDROID_JPEG_ENCODER_H__ #define __ANDROID_JPEG_ENCODER_H__ -#include <libcamera/buffer.h> -#include <libcamera/span.h> +#include <libcamera/base/span.h> + +#include <libcamera/framebuffer.h> #include <libcamera/stream.h> class Encoder { public: - virtual ~Encoder() {}; + virtual ~Encoder() = default; virtual int configure(const libcamera::StreamConfiguration &cfg) = 0; - virtual int encode(const libcamera::FrameBuffer *source, - const libcamera::Span<uint8_t> &destination, - const libcamera::Span<const uint8_t> &exifData) = 0; + virtual int encode(const libcamera::FrameBuffer &source, + libcamera::Span<uint8_t> destination, + libcamera::Span<const uint8_t> exifData, + unsigned int quality) = 0; }; #endif /* __ANDROID_JPEG_ENCODER_H__ */ diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp index 510613cd..e6358ca9 100644 --- a/src/android/jpeg/encoder_libjpeg.cpp +++ b/src/android/jpeg/encoder_libjpeg.cpp @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * @@ -16,16 +16,17 @@ #include <unistd.h> #include <vector> +#include <libcamera/base/log.h> + #include <libcamera/camera.h> #include <libcamera/formats.h> #include <libcamera/pixel_format.h> #include "libcamera/internal/formats.h" -#include "libcamera/internal/log.h" using namespace libcamera; -LOG_DEFINE_CATEGORY(JPEG) +LOG_DECLARE_CATEGORY(JPEG) namespace { @@ -68,7 +69,6 @@ const struct JPEGPixelFormatInfo &findPixelInfo(const PixelFormat &format) } /* namespace */ EncoderLibJpeg::EncoderLibJpeg() - : quality_(95) { /* \todo Expand error handling coverage with a custom handler. */ compress_.err = jpeg_std_error(&jerr_); @@ -94,7 +94,6 @@ int EncoderLibJpeg::configure(const StreamConfiguration &cfg) compress_.input_components = info.colorSpace == JCS_GRAYSCALE ? 1 : 3; jpeg_set_defaults(&compress_); - jpeg_set_quality(&compress_, quality_, TRUE); pixelFormatInfo_ = &info.pixelFormatInfo; @@ -104,9 +103,9 @@ int EncoderLibJpeg::configure(const StreamConfiguration &cfg) return 0; } -void EncoderLibJpeg::compressRGB(const libcamera::MappedBuffer *frame) +void EncoderLibJpeg::compressRGB(Span<const uint8_t> frame) { - unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data()); + unsigned char *src = const_cast<unsigned char *>(frame.data()); /* \todo Stride information should come from buffer configuration. */ unsigned int stride = pixelFormatInfo_->stride(compress_.image_width, 0); @@ -122,7 +121,7 @@ void EncoderLibJpeg::compressRGB(const libcamera::MappedBuffer *frame) * Compress the incoming buffer from a supported NV format. * This naively unpacks the semi-planar NV12 to a YUV888 format for libjpeg. */ -void EncoderLibJpeg::compressNV(const libcamera::MappedBuffer *frame) +void EncoderLibJpeg::compressNV(Span<const uint8_t> frame) { uint8_t tmprowbuf[compress_.image_width * 3]; @@ -144,7 +143,7 @@ void EncoderLibJpeg::compressNV(const libcamera::MappedBuffer *frame) unsigned int cb_pos = nvSwap_ ? 1 : 0; unsigned int cr_pos = nvSwap_ ? 0 : 1; - const unsigned char *src = static_cast<unsigned char *>(frame->maps()[0].data()); + const unsigned char *src = frame.data(); const unsigned char *src_c = src + y_stride * compress_.image_height; JSAMPROW row_pointer[1]; @@ -179,20 +178,27 @@ void EncoderLibJpeg::compressNV(const libcamera::MappedBuffer *frame) } } -int EncoderLibJpeg::encode(const FrameBuffer *source, - const libcamera::Span<uint8_t> &dest, - const libcamera::Span<const uint8_t> &exifData) +int EncoderLibJpeg::encode(const FrameBuffer &source, Span<uint8_t> dest, + Span<const uint8_t> exifData, unsigned int quality) { - MappedFrameBuffer frame(source, PROT_READ); + MappedFrameBuffer frame(&source, PROT_READ); if (!frame.isValid()) { LOG(JPEG, Error) << "Failed to map FrameBuffer : " << strerror(frame.error()); return frame.error(); } + return encode(frame.maps()[0], dest, exifData, quality); +} + +int EncoderLibJpeg::encode(Span<const uint8_t> src, Span<uint8_t> dest, + Span<const uint8_t> exifData, unsigned int quality) +{ unsigned char *destination = dest.data(); unsigned long size = dest.size(); + jpeg_set_quality(&compress_, quality, TRUE); + /* * The jpeg_mem_dest will reallocate if the required size is not * sufficient. That means the output won't be written to the correct @@ -215,9 +221,9 @@ int EncoderLibJpeg::encode(const FrameBuffer *source, << "x" << compress_.image_height; if (nv_) - compressNV(&frame); + compressNV(src); else - compressRGB(&frame); + compressRGB(src); jpeg_finish_compress(&compress_); diff --git a/src/android/jpeg/encoder_libjpeg.h b/src/android/jpeg/encoder_libjpeg.h index 1e8df05a..14bf8922 100644 --- a/src/android/jpeg/encoder_libjpeg.h +++ b/src/android/jpeg/encoder_libjpeg.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * @@ -9,8 +9,8 @@ #include "encoder.h" -#include "libcamera/internal/buffer.h" #include "libcamera/internal/formats.h" +#include "libcamera/internal/framebuffer.h" #include <jpeglib.h> @@ -21,19 +21,22 @@ public: ~EncoderLibJpeg(); int configure(const libcamera::StreamConfiguration &cfg) override; - int encode(const libcamera::FrameBuffer *source, - const libcamera::Span<uint8_t> &destination, - const libcamera::Span<const uint8_t> &exifData) override; + int encode(const libcamera::FrameBuffer &source, + libcamera::Span<uint8_t> destination, + libcamera::Span<const uint8_t> exifData, + unsigned int quality) override; + int encode(libcamera::Span<const uint8_t> source, + libcamera::Span<uint8_t> destination, + libcamera::Span<const uint8_t> exifData, + unsigned int quality); private: - void compressRGB(const libcamera::MappedBuffer *frame); - void compressNV(const libcamera::MappedBuffer *frame); + void compressRGB(libcamera::Span<const uint8_t> frame); + void compressNV(libcamera::Span<const uint8_t> frame); struct jpeg_compress_struct compress_; struct jpeg_error_mgr jerr_; - unsigned int quality_; - const libcamera::PixelFormatInfo *pixelFormatInfo_; bool nv_; diff --git a/src/android/jpeg/exif.cpp b/src/android/jpeg/exif.cpp index 32cf8974..0ba4cb85 100644 --- a/src/android/jpeg/exif.cpp +++ b/src/android/jpeg/exif.cpp @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * @@ -7,7 +7,15 @@ #include "exif.h" -#include "libcamera/internal/log.h" +#include <cmath> +#include <iomanip> +#include <map> +#include <sstream> +#include <tuple> +#include <uchar.h> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> using namespace libcamera; @@ -35,7 +43,8 @@ enum class _ExifTag { * data can be obtained using the data() method. */ Exif::Exif() - : valid_(false), data_(nullptr), exifData_(0), size_(0) + : valid_(false), data_(nullptr), order_(EXIF_BYTE_ORDER_INTEL), + exifData_(0), size_(0) { /* Create an ExifMem allocator to construct entries. */ mem_ = exif_mem_new_default(); @@ -59,7 +68,7 @@ Exif::Exif() * Big-Endian: EXIF_BYTE_ORDER_MOTOROLA * Little Endian: EXIF_BYTE_ORDER_INTEL */ - exif_data_set_byte_order(data_, EXIF_BYTE_ORDER_INTEL); + exif_data_set_byte_order(data_, order_); setString(EXIF_IFD_EXIF, EXIF_TAG_EXIF_VERSION, EXIF_FORMAT_UNDEFINED, "0231"); @@ -73,8 +82,16 @@ Exif::~Exif() if (exifData_) free(exifData_); - if (data_) + if (data_) { + /* + * Reset thumbnail data to avoid getting double-freed by + * libexif. It is owned by the caller (i.e. PostProcessorJpeg). + */ + data_->data = nullptr; + data_->size = 0; + exif_data_unref(data_); + } if (mem_) exif_mem_unref(mem_); @@ -138,13 +155,23 @@ 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); if (!entry) return; - exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, item); + exif_set_short(entry->data, order_, item); exif_entry_unref(entry); } @@ -154,31 +181,96 @@ void Exif::setLong(ExifIfd ifd, ExifTag tag, uint32_t item) if (!entry) return; - exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, item); + exif_set_long(entry->data, order_, item); exif_entry_unref(entry); } void Exif::setRational(ExifIfd ifd, ExifTag tag, ExifRational item) { - ExifEntry *entry = createEntry(ifd, tag); + setRational(ifd, tag, { &item, 1 }); +} + +void Exif::setRational(ExifIfd ifd, ExifTag tag, Span<const ExifRational> items) +{ + ExifEntry *entry = createEntry(ifd, tag, EXIF_FORMAT_RATIONAL, + items.size(), + items.size() * sizeof(ExifRational)); if (!entry) return; - exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, item); + for (size_t i = 0; i < items.size(); i++) + exif_set_rational(entry->data + i * sizeof(ExifRational), + order_, items[i]); exif_entry_unref(entry); } -void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, const std::string &item) +static const std::map<Exif::StringEncoding, std::array<uint8_t, 8>> stringEncodingCodes = { + { Exif::ASCII, { 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 } }, + { Exif::Unicode, { 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, 0x00 } }, +}; + +void Exif::setString(ExifIfd ifd, ExifTag tag, ExifFormat format, + const std::string &item, StringEncoding encoding) { - /* Pad 1 extra byte for null-terminated string in ASCII format. */ - size_t length = format == EXIF_FORMAT_ASCII ? - item.length() + 1 : item.length(); + std::string ascii; + size_t length; + const char *str; + std::vector<uint8_t> buf; + + if (format == EXIF_FORMAT_ASCII) { + ascii = utils::toAscii(item); + str = ascii.c_str(); + + /* Pad 1 extra byte to null-terminate the ASCII string. */ + length = ascii.length() + 1; + } else { + std::u16string u16str; + + auto encodingString = stringEncodingCodes.find(encoding); + if (encodingString != stringEncodingCodes.end()) { + buf = { + encodingString->second.begin(), + encodingString->second.end() + }; + } + + switch (encoding) { + case Unicode: + u16str = utf8ToUtf16(item); + + buf.resize(8 + u16str.size() * 2); + for (size_t i = 0; i < u16str.size(); i++) { + if (order_ == EXIF_BYTE_ORDER_INTEL) { + buf[8 + 2 * i] = u16str[i] & 0xff; + buf[8 + 2 * i + 1] = (u16str[i] >> 8) & 0xff; + } else { + buf[8 + 2 * i] = (u16str[i] >> 8) & 0xff; + buf[8 + 2 * i + 1] = u16str[i] & 0xff; + } + } + + break; + + case ASCII: + case NoEncoding: + buf.insert(buf.end(), item.begin(), item.end()); + break; + } + + str = reinterpret_cast<const char *>(buf.data()); + + /* + * Strings stored in different formats (EXIF_FORMAT_UNDEFINED) + * are not null-terminated. + */ + length = buf.size(); + } ExifEntry *entry = createEntry(ifd, tag, format, length, length); if (!entry) return; - memcpy(entry->data, item.c_str(), length); + memcpy(entry->data, str, length); exif_entry_unref(entry); } @@ -198,7 +290,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); @@ -213,19 +305,107 @@ 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 sstr; + sstr << std::setfill('0') << std::setw(3) << msec.count(); + std::string subsec = sstr.str(); + + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME, + EXIF_FORMAT_ASCII, subsec); + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_ORIGINAL, + EXIF_FORMAT_ASCII, subsec); + setString(EXIF_IFD_EXIF, EXIF_TAG_SUB_SEC_TIME_DIGITIZED, + EXIF_FORMAT_ASCII, subsec); +} + +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 */ + ExifRational ts[] = { + { static_cast<ExifLong>(tm.tm_hour), 1 }, + { static_cast<ExifLong>(tm.tm_min), 1 }, + { static_cast<ExifLong>(tm.tm_sec), 1 }, + }; + + setRational(EXIF_IFD_GPS, static_cast<ExifTag>(EXIF_TAG_GPS_TIME_STAMP), + 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) +{ + ExifRational coords[] = { + { static_cast<ExifLong>(deg), 1 }, + { static_cast<ExifLong>(min), 1 }, + { static_cast<ExifLong>(sec), 1 }, + }; + + setRational(ifd, tag, coords); +} + +/* + * \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_LONGITUDE), + 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, NoEncoding); } void Exif::setOrientation(int orientation) @@ -237,19 +417,94 @@ void Exif::setOrientation(int orientation) value = 1; break; case 90: - value = 8; + value = 6; break; case 180: value = 3; break; case 270: - value = 6; + value = 8; break; } setShort(EXIF_IFD_0, EXIF_TAG_ORIENTATION, value); } +/* + * The thumbnail data should remain valid until the Exif object is destroyed. + * Failing to do so, might result in no thumbnail data being set even after a + * call to Exif::setThumbnail(). + */ +void Exif::setThumbnail(Span<const unsigned char> thumbnail, + Compression compression) +{ + data_->data = const_cast<unsigned char *>(thumbnail.data()); + data_->size = thumbnail.size(); + + 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 + * + * \return \a str in UTF-16 + */ +std::u16string Exif::utf8ToUtf16(const std::string &str) +{ + 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<size_t>(-2) || + rc == static_cast<size_t>(-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 f04cefce..23b0e097 100644 --- a/src/android/jpeg/exif.h +++ b/src/android/jpeg/exif.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Google Inc. * @@ -7,13 +7,15 @@ #ifndef __ANDROID_JPEG_EXIF_H__ #define __ANDROID_JPEG_EXIF_H__ +#include <chrono> #include <string> #include <time.h> #include <libexif/exif-data.h> +#include <libcamera/base/span.h> + #include <libcamera/geometry.h> -#include <libcamera/span.h> class Exif { @@ -21,12 +23,57 @@ public: Exif(); ~Exif(); + enum Compression { + None = 1, + 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, + Unicode = 2, + }; + void setMake(const std::string &make); void setModel(const std::string &model); void setOrientation(int orientation); void setSize(const libcamera::Size &size); - void setTimestamp(time_t timestamp); + void setThumbnail(libcamera::Span<const unsigned char> thumbnail, + Compression compression); + 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(); @@ -36,16 +83,26 @@ 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, - const std::string &item); + const std::string &item, + StringEncoding encoding = NoEncoding); void setRational(ExifIfd ifd, ExifTag tag, ExifRational item); + void setRational(ExifIfd ifd, ExifTag tag, + libcamera::Span<const ExifRational> items); + + 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_; ExifData *data_; ExifMem *mem_; + ExifByteOrder order_; unsigned char *exifData_; unsigned int size_; diff --git a/src/android/jpeg/post_processor_jpeg.cpp b/src/android/jpeg/post_processor_jpeg.cpp new file mode 100644 index 00000000..0e93f365 --- /dev/null +++ b/src/android/jpeg/post_processor_jpeg.cpp @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * post_processor_jpeg.cpp - JPEG Post Processor + */ + +#include "post_processor_jpeg.h" + +#include <chrono> + +#include "../camera_device.h" +#include "../camera_metadata.h" +#include "encoder_libjpeg.h" +#include "exif.h" + +#include <libcamera/base/log.h> + +#include <libcamera/formats.h> + +using namespace libcamera; +using namespace std::chrono_literals; + +LOG_DEFINE_CATEGORY(JPEG) + +PostProcessorJpeg::PostProcessorJpeg(CameraDevice *const device) + : cameraDevice_(device) +{ +} + +int PostProcessorJpeg::configure(const StreamConfiguration &inCfg, + const StreamConfiguration &outCfg) +{ + if (inCfg.size != outCfg.size) { + LOG(JPEG, Error) << "Mismatch of input and output stream sizes"; + return -EINVAL; + } + + if (outCfg.pixelFormat != formats::MJPEG) { + LOG(JPEG, Error) << "Output stream pixel format is not JPEG"; + return -EINVAL; + } + + streamSize_ = outCfg.size; + + thumbnailer_.configure(inCfg.size, inCfg.pixelFormat); + + encoder_ = std::make_unique<EncoderLibJpeg>(); + + return encoder_->configure(inCfg); +} + +void PostProcessorJpeg::generateThumbnail(const FrameBuffer &source, + const Size &targetSize, + unsigned int quality, + std::vector<unsigned char> *thumbnail) +{ + /* Stores the raw scaled-down thumbnail bytes. */ + std::vector<unsigned char> rawThumbnail; + + thumbnailer_.createThumbnail(source, targetSize, &rawThumbnail); + + StreamConfiguration thCfg; + thCfg.size = targetSize; + thCfg.pixelFormat = thumbnailer_.pixelFormat(); + int ret = thumbnailEncoder_.configure(thCfg); + + if (!rawThumbnail.empty() && !ret) { + /* + * \todo Avoid value-initialization of all elements of the + * vector. + */ + thumbnail->resize(rawThumbnail.size()); + + int jpeg_size = thumbnailEncoder_.encode(rawThumbnail, + *thumbnail, {}, quality); + thumbnail->resize(jpeg_size); + + LOG(JPEG, Debug) + << "Thumbnail compress returned " + << jpeg_size << " bytes"; + } +} + +int PostProcessorJpeg::process(const FrameBuffer &source, + CameraBuffer *destination, + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata) +{ + if (!encoder_) + return 0; + + ASSERT(destination->numPlanes() == 1); + + camera_metadata_ro_entry_t entry; + int ret; + + /* Set EXIF metadata for various tags. */ + Exif exif; + exif.setMake(cameraDevice_->maker()); + exif.setModel(cameraDevice_->model()); + + ret = requestMetadata.getEntry(ANDROID_JPEG_ORIENTATION, &entry); + + const uint32_t jpegOrientation = ret ? *entry.data.i32 : 0; + resultMetadata->addEntry(ANDROID_JPEG_ORIENTATION, jpegOrientation); + exif.setOrientation(jpegOrientation); + + exif.setSize(streamSize_); + /* + * We set the frame's EXIF timestamp as the time of encode. + * Since the precision we need for EXIF timestamp is only one + * second, it is good enough. + */ + exif.setTimestamp(std::time(nullptr), 0ms); + + ret = resultMetadata->getEntry(ANDROID_SENSOR_EXPOSURE_TIME, &entry); + exif.setExposureTime(ret ? *entry.data.i64 : 0); + ret = requestMetadata.getEntry(ANDROID_LENS_APERTURE, &entry); + if (ret) + exif.setAperture(*entry.data.f); + exif.setISO(100); + exif.setFlash(Exif::Flash::FlashNotPresent); + exif.setWhiteBalance(Exif::WhiteBalance::Auto); + + exif.setFocalLength(1.0); + + ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_TIMESTAMP, &entry); + if (ret) { + exif.setGPSDateTimestamp(*entry.data.i64); + resultMetadata->addEntry(ANDROID_JPEG_GPS_TIMESTAMP, + *entry.data.i64); + } + + ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_SIZE, &entry); + if (ret) { + const int32_t *data = entry.data.i32; + Size thumbnailSize = { static_cast<uint32_t>(data[0]), + static_cast<uint32_t>(data[1]) }; + + ret = requestMetadata.getEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, &entry); + uint8_t quality = ret ? *entry.data.u8 : 95; + resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_QUALITY, quality); + + if (thumbnailSize != Size(0, 0)) { + std::vector<unsigned char> thumbnail; + generateThumbnail(source, thumbnailSize, quality, &thumbnail); + if (!thumbnail.empty()) + exif.setThumbnail(thumbnail, Exif::Compression::JPEG); + } + + resultMetadata->addEntry(ANDROID_JPEG_THUMBNAIL_SIZE, data, 2); + } + + ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_COORDINATES, &entry); + if (ret) { + exif.setGPSLocation(entry.data.d); + resultMetadata->addEntry(ANDROID_JPEG_GPS_COORDINATES, + entry.data.d, 3); + } + + ret = requestMetadata.getEntry(ANDROID_JPEG_GPS_PROCESSING_METHOD, &entry); + if (ret) { + std::string method(entry.data.u8, entry.data.u8 + entry.count); + exif.setGPSMethod(method); + resultMetadata->addEntry(ANDROID_JPEG_GPS_PROCESSING_METHOD, + entry.data.u8, entry.count); + } + + if (exif.generate() != 0) + LOG(JPEG, Error) << "Failed to generate valid EXIF data"; + + ret = requestMetadata.getEntry(ANDROID_JPEG_QUALITY, &entry); + const uint8_t quality = ret ? *entry.data.u8 : 95; + resultMetadata->addEntry(ANDROID_JPEG_QUALITY, quality); + + int jpeg_size = encoder_->encode(source, destination->plane(0), + exif.data(), quality); + if (jpeg_size < 0) { + LOG(JPEG, Error) << "Failed to encode stream image"; + return jpeg_size; + } + + /* Fill in the JPEG blob header. */ + uint8_t *resultPtr = destination->plane(0).data() + + destination->jpegBufferSize(cameraDevice_->maxJpegBufferSize()) + - sizeof(struct camera3_jpeg_blob); + auto *blob = reinterpret_cast<struct camera3_jpeg_blob *>(resultPtr); + blob->jpeg_blob_id = CAMERA3_JPEG_BLOB_ID; + blob->jpeg_size = jpeg_size; + + /* Update the JPEG result Metadata. */ + resultMetadata->addEntry(ANDROID_JPEG_SIZE, jpeg_size); + + return 0; +} diff --git a/src/android/jpeg/post_processor_jpeg.h b/src/android/jpeg/post_processor_jpeg.h new file mode 100644 index 00000000..5c399be9 --- /dev/null +++ b/src/android/jpeg/post_processor_jpeg.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * post_processor_jpeg.h - JPEG Post Processor + */ +#ifndef __ANDROID_POST_PROCESSOR_JPEG_H__ +#define __ANDROID_POST_PROCESSOR_JPEG_H__ + +#include "../post_processor.h" +#include "encoder_libjpeg.h" +#include "thumbnailer.h" + +#include <libcamera/geometry.h> + +#include "libcamera/internal/framebuffer.h" + +class CameraDevice; + +class PostProcessorJpeg : public PostProcessor +{ +public: + PostProcessorJpeg(CameraDevice *const device); + + int configure(const libcamera::StreamConfiguration &incfg, + const libcamera::StreamConfiguration &outcfg) override; + int process(const libcamera::FrameBuffer &source, + CameraBuffer *destination, + const CameraMetadata &requestMetadata, + CameraMetadata *resultMetadata) override; + +private: + void generateThumbnail(const libcamera::FrameBuffer &source, + const libcamera::Size &targetSize, + unsigned int quality, + std::vector<unsigned char> *thumbnail); + + CameraDevice *const cameraDevice_; + std::unique_ptr<Encoder> encoder_; + libcamera::Size streamSize_; + EncoderLibJpeg thumbnailEncoder_; + Thumbnailer thumbnailer_; +}; + +#endif /* __ANDROID_POST_PROCESSOR_JPEG_H__ */ diff --git a/src/android/jpeg/thumbnailer.cpp b/src/android/jpeg/thumbnailer.cpp new file mode 100644 index 00000000..5cb00744 --- /dev/null +++ b/src/android/jpeg/thumbnailer.cpp @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * thumbnailer.cpp - Simple image thumbnailer + */ + +#include "thumbnailer.h" + +#include <libcamera/base/log.h> + +#include <libcamera/formats.h> + +using namespace libcamera; + +LOG_DEFINE_CATEGORY(Thumbnailer) + +Thumbnailer::Thumbnailer() + : valid_(false) +{ +} + +void Thumbnailer::configure(const Size &sourceSize, PixelFormat pixelFormat) +{ + sourceSize_ = sourceSize; + pixelFormat_ = pixelFormat; + + if (pixelFormat_ != formats::NV12) { + LOG(Thumbnailer, Error) + << "Failed to configure: Pixel Format " + << pixelFormat_.toString() << " unsupported."; + return; + } + + valid_ = true; +} + +void Thumbnailer::createThumbnail(const FrameBuffer &source, + const Size &targetSize, + std::vector<unsigned char> *destination) +{ + MappedFrameBuffer frame(&source, PROT_READ); + if (!frame.isValid()) { + LOG(Thumbnailer, Error) + << "Failed to map FrameBuffer : " + << strerror(frame.error()); + return; + } + + if (!valid_) { + LOG(Thumbnailer, Error) << "Config is unconfigured or invalid."; + return; + } + + const unsigned int sw = sourceSize_.width; + const unsigned int sh = sourceSize_.height; + const unsigned int tw = targetSize.width; + const unsigned int th = targetSize.height; + + ASSERT(tw % 2 == 0 && th % 2 == 0); + + /* Image scaling block implementing nearest-neighbour algorithm. */ + unsigned char *src = static_cast<unsigned char *>(frame.maps()[0].data()); + unsigned char *srcC = src + sh * sw; + unsigned char *srcCb, *srcCr; + unsigned char *dstY, *srcY; + + size_t dstSize = (th * tw) + ((th / 2) * tw); + destination->resize(dstSize); + unsigned char *dst = destination->data(); + unsigned char *dstC = dst + th * tw; + + for (unsigned int y = 0; y < th; y += 2) { + unsigned int sourceY = (sh * y + th / 2) / th; + + dstY = dst + y * tw; + srcY = src + sw * sourceY; + srcCb = srcC + (sourceY / 2) * sw + 0; + srcCr = srcC + (sourceY / 2) * sw + 1; + + for (unsigned int x = 0; x < tw; x += 2) { + unsigned int sourceX = (sw * x + tw / 2) / tw; + + dstY[x] = srcY[sourceX]; + dstY[tw + x] = srcY[sw + sourceX]; + dstY[x + 1] = srcY[sourceX + 1]; + dstY[tw + x + 1] = srcY[sw + sourceX + 1]; + + dstC[(y / 2) * tw + x + 0] = srcCb[(sourceX / 2) * 2]; + dstC[(y / 2) * tw + x + 1] = srcCr[(sourceX / 2) * 2]; + } + } +} diff --git a/src/android/jpeg/thumbnailer.h b/src/android/jpeg/thumbnailer.h new file mode 100644 index 00000000..68cbf743 --- /dev/null +++ b/src/android/jpeg/thumbnailer.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Google Inc. + * + * thumbnailer.h - Simple image thumbnailer + */ +#ifndef __ANDROID_JPEG_THUMBNAILER_H__ +#define __ANDROID_JPEG_THUMBNAILER_H__ + +#include <libcamera/geometry.h> + +#include "libcamera/internal/formats.h" +#include "libcamera/internal/framebuffer.h" + +class Thumbnailer +{ +public: + Thumbnailer(); + + void configure(const libcamera::Size &sourceSize, + libcamera::PixelFormat pixelFormat); + void createThumbnail(const libcamera::FrameBuffer &source, + const libcamera::Size &targetSize, + std::vector<unsigned char> *dest); + const libcamera::PixelFormat &pixelFormat() const { return pixelFormat_; } + +private: + libcamera::PixelFormat pixelFormat_; + libcamera::Size sourceSize_; + + bool valid_; +}; + +#endif /* __ANDROID_JPEG_THUMBNAILER_H__ */ |