jpegr library: add multi-picture format support
test: jpegr_test
bug: b/264715926
Change-Id: I1bd299ddc0435e54f7c8554b92b3cd6eb2f6eb2b
(cherry picked from commit 283c64b190e74e63064524f6eb598654cbe98f4c)
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 78d1912..0c03ede 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -32,6 +32,7 @@
"jpegr.cpp",
"recoverymapmath.cpp",
"jpegrutils.cpp",
+ "multipictureformat.cpp",
],
shared_libs: [
@@ -40,6 +41,7 @@
"libjpegencoder",
"libjpegdecoder",
"liblog",
+ "libutils",
],
static_libs: ["libskia"],
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h
index 581806c..4145853 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 @@
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 @@
* 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 @@
* <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 @@
* </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 0000000..7dca916
--- /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 828af2d..09a4315 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 @@
/* 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 @@
/* 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 @@
/* 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 @@
/* 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 @@
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 @@
/* 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 @@
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::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 @@
}
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::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::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::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 @@
// (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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 49526c8..38b78ad 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 @@
* @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 @@
public:
XMPXmlHandler() : XmlHandler() {
- gContainerItemState = NotStrarted;
+ state = NotStrarted;
}
enum ParseState {
@@ -70,11 +121,11 @@
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 @@
}
virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
- if (gContainerItemState == Started) {
- gContainerItemState = Done;
+ if (state == Started) {
+ state = Done;
lastAttributeName = "";
}
return context.GetResult();
@@ -91,7 +142,7 @@
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 @@
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 @@
}
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 @@
}
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 @@
}
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 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 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 @@
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 @@
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 @@
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 0000000..a219aef
--- /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 5a4edb2..61b3db9 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -40,6 +40,7 @@
"libjpegencoder",
"libjpegrecoverymap",
"libskia",
+ "libutils",
],
}
diff --git a/libs/jpegrecoverymap/tests/jpegr_test.cpp b/libs/jpegrecoverymap/tests/jpegr_test.cpp
index 7a3133d..df212e1 100644
--- a/libs/jpegrecoverymap/tests/jpegr_test.cpp
+++ b/libs/jpegrecoverymap/tests/jpegr_test.cpp
@@ -177,11 +177,10 @@
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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());