diff options
| author | 2023-02-24 19:23:36 +0000 | |
|---|---|---|
| committer | 2023-02-24 19:23:36 +0000 | |
| commit | d046f9888c228f83f7874a7fa2273d06f637ab9c (patch) | |
| tree | cdec1691d931d0e13edc304844498e4d3d4c3703 | |
| parent | 5b6ca88e1444ebba97db26a226c9b3a8f169062b (diff) | |
| parent | 61ede360647474abbaa1b3f4076c1c6a6a3d8e2b (diff) | |
Merge "jpegr library: add multi-picture format support" into udc-dev
| -rw-r--r-- | libs/jpegrecoverymap/Android.bp | 2 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h | 65 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h | 76 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/jpegr.cpp | 149 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/jpegrutils.cpp | 137 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/multipictureformat.cpp | 94 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/tests/Android.bp | 1 | ||||
| -rw-r--r-- | libs/jpegrecoverymap/tests/jpegr_test.cpp | 19 |
8 files changed, 448 insertions, 95 deletions
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 78d1912d24..0c03ede9e8 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -32,6 +32,7 @@ cc_library { "jpegr.cpp", "recoverymapmath.cpp", "jpegrutils.cpp", + "multipictureformat.cpp", ], shared_libs: [ @@ -40,6 +41,7 @@ cc_library { "libjpegencoder", "libjpegdecoder", "liblog", + "libutils", ], static_libs: ["libskia"], diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h index 581806c54e..41458532b9 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h @@ -18,6 +18,7 @@ #define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H #include <jpegrecoverymap/jpegr.h> +#include <utils/RefBase.h> #include <sstream> #include <stdint.h> @@ -27,6 +28,26 @@ namespace android::jpegrecoverymap { struct jpegr_metadata; +/* + * Mutable data structure. Holds information for metadata. + */ +class DataStruct : public RefBase { +private: + void* data; + int writePos; + int length; + ~DataStruct(); + +public: + DataStruct(int s); + void* getData(); + int getLength(); + int getBytesWritten(); + bool write8(uint8_t value); + bool write16(uint16_t value); + bool write32(uint32_t value); + bool write(const void* src, int size); +}; /* * Helper function used for writing data to destination. @@ -51,12 +72,10 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata); /* - * This method generates XMP metadata. + * This method generates XMP metadata for the primary image. * * below is an example of the XMP metadata that this function generates where * secondary_image_length = 1000 - * max_content_boost = 8.0 - * min_content_boost = 0.5 * * <x:xmpmeta * xmlns:x="adobe:ns:meta/" @@ -65,8 +84,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> * <rdf:Description * xmlns:Container="http://ns.google.com/photos/1.0/container/" - * xmlns:Item="http://ns.google.com/photos/1.0/container/item/" - * xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/"> + * xmlns:Item="http://ns.google.com/photos/1.0/container/item/"> * <Container:Directory> * <rdf:Seq> * <rdf:li> @@ -78,10 +96,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * <Container:Item * Item:Semantic="RecoveryMap" * Item:Mime="image/jpeg" - * Item:Length="1000" - * RecoveryMap:Version="1" - * RecoveryMap:MaxContentBoost="8.0" - * RecoveryMap:MinContentBoost="0.5"/> + * Item:Length="1000"/> * </rdf:li> * </rdf:Seq> * </Container:Directory> @@ -90,10 +105,40 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * </x:xmpmeta> * * @param secondary_image_length length of secondary image + * @return XMP metadata in type of string + */ +std::string generateXmpForPrimaryImage(int secondary_image_length); + +/* + * This method generates XMP metadata for the recovery map image. + * + * below is an example of the XMP metadata that this function generates where + * max_content_boost = 8.0 + * min_content_boost = 0.5 + * + * <x:xmpmeta + * xmlns:x="adobe:ns:meta/" + * x:xmptk="Adobe XMP Core 5.1.2"> + * <rdf:RDF + * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + * <rdf:Description + * xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" + * hdrgm:Version="1" + * hdrgm:GainMapMin="0.5" + * hdrgm:GainMapMax="8.5" + * hdrgm:Gamma="1" + * hdrgm:OffsetSDR="0" + * hdrgm:OffsetHDR="0" + * hdrgm:HDRCapacityMin="0.5" + * hdrgm:HDRCapacityMax="8.5" + * hdrgm:BaseRendition="SDR"/> + * </rdf:RDF> + * </x:xmpmeta> + * * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ -std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); + std::string generateXmpForSecondaryImage(jpegr_metadata& metadata); } // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h new file mode 100644 index 0000000000..7dca91637e --- /dev/null +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H +#define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H + +#include <jpegrecoverymap/jpegrutils.h> + +namespace android::jpegrecoverymap { +static constexpr uint32_t EndianSwap32(uint32_t value) { + return ((value & 0xFF) << 24) | + ((value & 0xFF00) << 8) | + ((value & 0xFF0000) >> 8) | + (value >> 24); +} +static inline uint16_t EndianSwap16(uint16_t value) { + return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8)); +} +#define USE_BIG_ENDIAN true +#if USE_BIG_ENDIAN + #define Endian_SwapBE32(n) EndianSwap32(n) + #define Endian_SwapBE16(n) EndianSwap16(n) +#else + #define Endian_SwapBE32(n) (n) + #define Endian_SwapBE16(n) (n) +#endif + +constexpr size_t kNumPictures = 2; +constexpr size_t kMpEndianSize = 4; +constexpr uint16_t kTagSerializedCount = 3; +constexpr uint32_t kTagSize = 12; + +constexpr uint16_t kTypeLong = 0x4; +constexpr uint16_t kTypeUndefined = 0x7; + +static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'}; +constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00}; +constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A}; + +constexpr uint16_t kVersionTag = 0xB000; +constexpr uint16_t kVersionType = kTypeUndefined; +constexpr uint32_t kVersionCount = 4; +constexpr size_t kVersionSize = 4; +constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'}; + +constexpr uint16_t kNumberOfImagesTag = 0xB001; +constexpr uint16_t kNumberOfImagesType = kTypeLong; +constexpr uint32_t kNumberOfImagesCount = 1; + +constexpr uint16_t kMPEntryTag = 0xB002; +constexpr uint16_t kMPEntryType = kTypeUndefined; +constexpr uint32_t kMPEntrySize = 16; + +constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000; +constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000; + +size_t calculateMpfSize(); +sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, + int secondary_image_size, int secondary_image_offset); + +} // namespace android::jpegrecoverymap + +#endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H diff --git a/libs/jpegrecoverymap/jpegr.cpp b/libs/jpegrecoverymap/jpegr.cpp index 828af2d289..09a4315480 100644 --- a/libs/jpegrecoverymap/jpegr.cpp +++ b/libs/jpegrecoverymap/jpegr.cpp @@ -19,6 +19,7 @@ #include <jpegrecoverymap/jpegdecoderhelper.h> #include <jpegrecoverymap/recoverymapmath.h> #include <jpegrecoverymap/jpegrutils.h> +#include <jpegrecoverymap/multipictureformat.h> #include <image_io/jpeg/jpeg_marker.h> #include <image_io/jpeg/jpeg_info.h> @@ -105,10 +106,10 @@ static const map< /* Encode API-0 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest, - int quality, - jr_exif_ptr exif) { + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -167,11 +168,11 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, /* Encode API-1 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr uncompressed_yuv_420_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest, - int quality, - jr_exif_ptr exif) { + jr_uncompressed_ptr uncompressed_yuv_420_image, + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest, + int quality, + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { @@ -231,10 +232,10 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, /* Encode API-2 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest) { + jr_uncompressed_ptr uncompressed_yuv_420_image, + jr_compressed_ptr compressed_jpeg_image, + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || compressed_jpeg_image == nullptr @@ -276,9 +277,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, /* Encode API-3 */ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, - jr_compressed_ptr compressed_jpeg_image, - jpegr_transfer_function hdr_tf, - jr_compressed_ptr dest) { + jr_compressed_ptr compressed_jpeg_image, + jpegr_transfer_function hdr_tf, + jr_compressed_ptr dest) { if (uncompressed_p010_image == nullptr || compressed_jpeg_image == nullptr || dest == nullptr) { @@ -327,8 +328,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } -status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, - jr_info_ptr jpegr_info) { +status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -349,9 +349,9 @@ status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, /* Decode API */ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, - jr_uncompressed_ptr dest, - jr_exif_ptr exif, - bool request_sdr) { + jr_uncompressed_ptr dest, + jr_exif_ptr exif, + bool request_sdr) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -399,8 +399,8 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); - if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()), - jpeg_decoder.getXMPSize(), &metadata)) { + if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()), + recovery_map_decoder.getXMPSize(), &metadata)) { return ERROR_JPEGR_DECODE_ERROR; } @@ -409,7 +409,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, } status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map, - jr_compressed_ptr dest) { + jr_compressed_ptr dest) { if (uncompressed_recovery_map == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -493,10 +493,10 @@ void JobQueue::reset() { } status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_p010_image, - jpegr_transfer_function hdr_tf, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { + jr_uncompressed_ptr uncompressed_p010_image, + jpegr_transfer_function hdr_tf, + jr_metadata_ptr metadata, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_p010_image == nullptr || metadata == nullptr @@ -637,9 +637,9 @@ status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ima } status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, - jr_uncompressed_ptr uncompressed_recovery_map, - jr_metadata_ptr metadata, - jr_uncompressed_ptr dest) { + jr_uncompressed_ptr uncompressed_recovery_map, + jr_metadata_ptr metadata, + jr_uncompressed_ptr dest) { if (uncompressed_yuv_420_image == nullptr || uncompressed_recovery_map == nullptr || metadata == nullptr @@ -721,8 +721,8 @@ status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image, } status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr primary_image, - jr_compressed_ptr recovery_map) { + jr_compressed_ptr primary_image, + jr_compressed_ptr recovery_map) { if (compressed_jpegr_image == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -771,7 +771,7 @@ status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_j status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, - jr_compressed_ptr dest) { + jr_compressed_ptr dest) { if (compressed_jpegr_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -790,11 +790,22 @@ status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, // (Required, XMP package) APP1 (ff e1) // 2 bytes of length (2 + 29 + length of xmp package) // name space ("http://ns.adobe.com/xap/1.0/\0") -// xmp +// XMP +// +// (Required, MPF package) APP2 (ff e2) +// 2 bytes of length +// MPF // // (Required) primary image (without the first two bytes (SOI), may have other packages) // -// (Required) secondary image (the recovery map) +// SOI (ff d8) +// +// (Required, XMP package) APP1 (ff e1) +// 2 bytes of length (2 + 29 + length of xmp package) +// name space ("http://ns.adobe.com/xap/1.0/\0") +// XMP +// +// (Required) secondary image (the recovery map, without the first two bytes (SOI)) // // Metadata versions we are using: // ECMA TR-98 for JFIF marker @@ -802,10 +813,10 @@ status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, // Adobe XMP spec part 3 for XMP marker // ICC v4.3 spec for ICC status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, - jr_compressed_ptr compressed_recovery_map, - jr_exif_ptr exif, - jr_metadata_ptr metadata, - jr_compressed_ptr dest) { + jr_compressed_ptr compressed_recovery_map, + jr_exif_ptr exif, + jr_metadata_ptr metadata, + jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr || metadata == nullptr @@ -815,6 +826,10 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, int pos = 0; + const string xmp_primary = generateXmpForPrimaryImage(compressed_recovery_map->length); + const string xmp_secondary = generateXmpForSecondaryImage(*metadata); + + // Begin primary image // Write SOI JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); @@ -833,13 +848,12 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, // Prepare and write XMP { - const string xmp = generateXmp(compressed_recovery_map->length, *metadata); const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator // 2 bytes: representing the length of the package // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", // x bytes: length of xmp packet - const int length = 2 + nameSpaceLength + xmp.size(); + const int length = 2 + nameSpaceLength + xmp_primary.size(); const uint8_t lengthH = ((length >> 8) & 0xff); const uint8_t lengthL = (length & 0xff); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); @@ -847,15 +861,59 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); + } + + // Prepare and write MPF + { + const int length = 2 + calculateMpfSize(); + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + int primary_image_size = pos + length + compressed_jpeg_image->length; + int secondary_image_offset = primary_image_size; + int secondary_image_size = xmp_secondary.size() + compressed_recovery_map->length; + sp<DataStruct> mpf = generateMpf(0, /* primary_image_offset */ + primary_image_size, + secondary_image_offset, + secondary_image_size); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos)); } // Write primary image JPEGR_CHECK(Write(dest, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); + // Finish primary image + + // Begin secondary image (recovery map) + // Write SOI + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + + // Prepare and write XMP + { + const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator + // 2 bytes: representing the length of the package + // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", + // x bytes: length of xmp packet + const int length = 2 + nameSpaceLength + xmp_secondary.size(); + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); + JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); + JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos)); + JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos)); + } // Write secondary image - JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); + JPEGR_CHECK(Write(dest, + (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos)); // Set back length dest->length = pos; @@ -864,8 +922,7 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return NO_ERROR; } -status_t JpegR::toneMap(jr_uncompressed_ptr src, - jr_uncompressed_ptr dest) { +status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { if (src == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } diff --git a/libs/jpegrecoverymap/jpegrutils.cpp b/libs/jpegrecoverymap/jpegrutils.cpp index 49526c800f..38b78ad19c 100644 --- a/libs/jpegrecoverymap/jpegrutils.cpp +++ b/libs/jpegrecoverymap/jpegrutils.cpp @@ -15,18 +15,19 @@ */ #include <jpegrecoverymap/jpegrutils.h> +#include <utils/Log.h> #include <image_io/xml/xml_reader.h> #include <image_io/xml/xml_writer.h> #include <image_io/base/message_handler.h> #include <image_io/xml/xml_element_rules.h> #include <image_io/xml/xml_handler.h> #include <image_io/xml/xml_rule.h> +#include <cmath> using namespace photos_editing_formats::image_io; using namespace std; namespace android::jpegrecoverymap { - /* * Helper function used for generating XMP metadata. * @@ -34,12 +35,62 @@ namespace android::jpegrecoverymap { * @param suffix The suffix part of the name. * @return A name of the form "prefix:suffix". */ -string Name(const string &prefix, const string &suffix) { +static inline string Name(const string &prefix, const string &suffix) { std::stringstream ss; ss << prefix << ":" << suffix; return ss.str(); } +DataStruct::DataStruct(int s) { + data = malloc(s); + length = s; + memset(data, 0, s); + writePos = 0; +} + +DataStruct::~DataStruct() { + if (data != nullptr) { + free(data); + } +} + +void* DataStruct::getData() { + return data; +} + +int DataStruct::getLength() { + return length; +} + +int DataStruct::getBytesWritten() { + return writePos; +} + +bool DataStruct::write8(uint8_t value) { + uint8_t v = value; + return write(&v, 1); +} + +bool DataStruct::write16(uint16_t value) { + uint16_t v = value; + return write(&v, 2); +} +bool DataStruct::write32(uint32_t value) { + uint32_t v = value; + return write(&v, 4); +} + +bool DataStruct::write(const void* src, int size) { + if (writePos + size > length) { + ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", + writePos, size, length); + return false; + } + memcpy((uint8_t*) data + writePos, src, size); + writePos += size; + return true; +} + /* * Helper function used for writing data to destination. */ @@ -58,7 +109,7 @@ class XMPXmlHandler : public XmlHandler { public: XMPXmlHandler() : XmlHandler() { - gContainerItemState = NotStrarted; + state = NotStrarted; } enum ParseState { @@ -70,11 +121,11 @@ public: virtual DataMatchResult StartElement(const XmlTokenContext& context) { string val; if (context.BuildTokenValue(&val)) { - if (!val.compare(gContainerItemName)) { - gContainerItemState = Started; + if (!val.compare(containerName)) { + state = Started; } else { - if (gContainerItemState != Done) { - gContainerItemState = NotStrarted; + if (state != Done) { + state = NotStrarted; } } } @@ -82,8 +133,8 @@ public: } virtual DataMatchResult FinishElement(const XmlTokenContext& context) { - if (gContainerItemState == Started) { - gContainerItemState = Done; + if (state == Started) { + state = Done; lastAttributeName = ""; } return context.GetResult(); @@ -91,7 +142,7 @@ public: virtual DataMatchResult AttributeName(const XmlTokenContext& context) { string val; - if (gContainerItemState == Started) { + if (state == Started) { if (context.BuildTokenValue(&val)) { if (!val.compare(maxContentBoostAttrName)) { lastAttributeName = maxContentBoostAttrName; @@ -107,7 +158,7 @@ public: virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { string val; - if (gContainerItemState == Started) { + if (state == Started) { if (context.BuildTokenValue(&val, true)) { if (!lastAttributeName.compare(maxContentBoostAttrName)) { maxContentBoostStr = val; @@ -120,11 +171,11 @@ public: } bool getMaxContentBoost(float* max_content_boost) { - if (gContainerItemState == Done) { + if (state == Done) { stringstream ss(maxContentBoostStr); float val; if (ss >> val) { - *max_content_boost = val; + *max_content_boost = exp2(val); return true; } else { return false; @@ -135,11 +186,11 @@ public: } bool getMinContentBoost(float* min_content_boost) { - if (gContainerItemState == Done) { + if (state == Done) { stringstream ss(minContentBoostStr); float val; if (ss >> val) { - *min_content_boost = val; + *min_content_boost = exp2(val); return true; } else { return false; @@ -150,13 +201,13 @@ public: } private: - static const string gContainerItemName; + static const string containerName; static const string maxContentBoostAttrName; string maxContentBoostStr; static const string minContentBoostAttrName; string minContentBoostStr; string lastAttributeName; - ParseState gContainerItemState; + ParseState state; }; // GContainer XMP constants - URI and namespace prefix @@ -168,8 +219,7 @@ const string kConDirectory = Name(kContainerPrefix, "Directory"); const string kConItem = Name(kContainerPrefix, "Item"); // GContainer XMP constants - names for XMP handlers -const string XMPXmlHandler::gContainerItemName = kConItem; - +const string XMPXmlHandler::containerName = "rdf:Description"; // Item XMP constants - URI and namespace prefix const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; const string kItemPrefix = "Item"; @@ -185,17 +235,23 @@ const string kSemanticRecoveryMap = "RecoveryMap"; const string kMimeImageJpeg = "image/jpeg"; // RecoveryMap XMP constants - URI and namespace prefix -const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/"; -const string kRecoveryMapPrefix = "RecoveryMap"; +const string kRecoveryMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; +const string kRecoveryMapPrefix = "hdrgm"; // RecoveryMap XMP constants - element and attribute names -const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost"); -const string kMapMinContentBoost = Name(kRecoveryMapPrefix, "MinContentBoost"); const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); +const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin"); +const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax"); +const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma"); +const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR"); +const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR"); +const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin"); +const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax"); +const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); // RecoveryMap XMP constants - names for XMP handlers -const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost; -const string XMPXmlHandler::minContentBoostAttrName = kMapMinContentBoost; +const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; +const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; @@ -243,7 +299,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta return true; } -string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { +string generateXmpForPrimaryImage(int secondary_image_length) { const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector<string> kLiItem({string("rdf:li"), kConItem}); @@ -257,7 +313,6 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kContainerPrefix, kContainerUri); writer.WriteXmlns(kItemPrefix, kItemUri); - writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); writer.StartWritingElements(kConDirSeq); size_t item_depth = writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); @@ -267,9 +322,33 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + writer.FinishWriting(); + + return ss.str(); +} + +string generateXmpForSecondaryImage(jpegr_metadata& metadata) { + const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); + const vector<string> kLiItem({string("rdf:li"), kConItem}); + + std::stringstream ss; + photos_editing_formats::image_io::XmlWriter writer(ss); + writer.StartWritingElement("x:xmpmeta"); + writer.WriteXmlns("x", "adobe:ns:meta/"); + writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); + writer.StartWritingElement("rdf:RDF"); + writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + writer.StartWritingElement("rdf:Description"); + writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost); - writer.WriteAttributeNameAndValue(kMapMinContentBoost, metadata.minContentBoost); + writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); + writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); + writer.WriteAttributeNameAndValue(kMapGamma, "1"); + writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); + writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0"); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3"); + writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR"); writer.FinishWriting(); return ss.str(); diff --git a/libs/jpegrecoverymap/multipictureformat.cpp b/libs/jpegrecoverymap/multipictureformat.cpp new file mode 100644 index 0000000000..a219aef106 --- /dev/null +++ b/libs/jpegrecoverymap/multipictureformat.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <jpegrecoverymap/multipictureformat.h> +#include <jpegrecoverymap/jpegrutils.h> + +namespace android::jpegrecoverymap { +size_t calculateMpfSize() { + return sizeof(kMpfSig) + // Signature + kMpEndianSize + // Endianness + sizeof(uint32_t) + // Index IFD Offset + sizeof(uint16_t) + // Tag count + kTagSerializedCount * kTagSize + // 3 tags at 12 bytes each + sizeof(uint32_t) + // Attribute IFD offset + kNumPictures * kMPEntrySize; // MP Entries for each image +} + +sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, + int secondary_image_size, int secondary_image_offset) { + size_t mpf_size = calculateMpfSize(); + sp<DataStruct> dataStruct = new DataStruct(mpf_size); + + dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig)); +#if USE_BIG_ENDIAN + dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize); +#else + dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize); +#endif + + // Set the Index IFD offset be the position after the endianness value and this offset. + constexpr uint32_t indexIfdOffset = + static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig)); + dataStruct->write32(Endian_SwapBE32(indexIfdOffset)); + + // We will write 3 tags (version, number of images, MP entries). + dataStruct->write16(Endian_SwapBE16(kTagSerializedCount)); + + // Write the version tag. + dataStruct->write16(Endian_SwapBE16(kVersionTag)); + dataStruct->write16(Endian_SwapBE16(kVersionType)); + dataStruct->write32(Endian_SwapBE32(kVersionCount)); + dataStruct->write(kVersionExpected, kVersionSize); + + // Write the number of images. + dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag)); + dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType)); + dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount)); + dataStruct->write32(Endian_SwapBE32(kNumPictures)); + + // Write the MP entries. + dataStruct->write16(Endian_SwapBE16(kMPEntryTag)); + dataStruct->write16(Endian_SwapBE16(kMPEntryType)); + dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures)); + const uint32_t mpEntryOffset = + static_cast<uint32_t>(dataStruct->getBytesWritten() - // The bytes written so far + sizeof(kMpfSig) + // Excluding the MPF signature + sizeof(uint32_t) + // The 4 bytes for this offset + sizeof(uint32_t)); // The 4 bytes for the attribute IFD offset. + dataStruct->write32(Endian_SwapBE32(mpEntryOffset)); + + // Write the attribute IFD offset (zero because we don't write it). + dataStruct->write32(0); + + // Write the MP entries for primary image + dataStruct->write32( + Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary)); + dataStruct->write32(Endian_SwapBE32(primary_image_size)); + dataStruct->write32(Endian_SwapBE32(primary_image_offset)); + dataStruct->write16(0); + dataStruct->write16(0); + + // Write the MP entries for secondary image + dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg)); + dataStruct->write32(Endian_SwapBE32(secondary_image_size)); + dataStruct->write32(Endian_SwapBE32(secondary_image_offset)); + dataStruct->write16(0); + dataStruct->write16(0); + + return dataStruct; +} + +} // namespace android::jpegrecoverymap diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp index 5a4edb2a25..61b3db9c62 100644 --- a/libs/jpegrecoverymap/tests/Android.bp +++ b/libs/jpegrecoverymap/tests/Android.bp @@ -40,6 +40,7 @@ cc_test { "libjpegencoder", "libjpegrecoverymap", "libskia", + "libutils", ], } diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp index 7a3133d10c..df212e1fb1 100644 --- a/libs/jpegrecoverymap/tests/jpegr_test.cpp +++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp @@ -177,11 +177,10 @@ TEST_F(JpegRTest, writeXmpThenRead) { jpegr_metadata metadata_expected; metadata_expected.maxContentBoost = 1.25; metadata_expected.minContentBoost = 0.75; - int length_expected = 1000; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - std::string xmp = generateXmp(1000, metadata_expected); + std::string xmp = generateXmpForSecondaryImage(metadata_expected); std::vector<uint8_t> xmpData; xmpData.reserve(nameSpaceLength + xmp.size()); @@ -220,7 +219,7 @@ TEST_F(JpegRTest, encodeFromP010ThenDecode) { } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -237,7 +236,7 @@ TEST_F(JpegRTest, encodeFromP010ThenDecode) { } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_input.rgb10"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -281,7 +280,7 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -298,7 +297,7 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrThenDecode) { } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_input.rgb10"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -346,7 +345,7 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_yuv420p_jpeg_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -363,7 +362,7 @@ TEST_F(JpegRTest, encodeFromRawHdrAndSdrAndJpegThenDecode) { } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_yuv420p_jpeg_input.rgb10"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -427,7 +426,7 @@ TEST_F(JpegRTest, encodeFromJpegThenDecode) { } if (SAVE_ENCODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr"; + std::string filePath = "/sdcard/Documents/encoded_from_p010_jpeg_input.jpgr"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); @@ -444,7 +443,7 @@ TEST_F(JpegRTest, encodeFromJpegThenDecode) { } if (SAVE_DECODING_RESULT) { // Output image data to file - std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10"; + std::string filePath = "/sdcard/Documents/decoded_from_p010_jpeg_input.rgb10"; std::ofstream imageFile(filePath.c_str(), std::ofstream::binary); if (!imageFile.is_open()) { ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str()); |