JPEGR Decoder. Inital implementation
* Implements JPEGR Decoding
* Doesn't implement XMP data parsing
* Implements retrieval of image data
Bug: b/252835416
Test: build
Change-Id: I62c7c323842b8d87866c43e85c1d7238e5b9b0d5
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp
index 0375915..54af7c9 100644
--- a/libs/jpegrecoverymap/Android.bp
+++ b/libs/jpegrecoverymap/Android.bp
@@ -31,6 +31,7 @@
srcs: [
"recoverymap.cpp",
"recoverymapmath.cpp",
+ "recoverymaputils.cpp",
],
shared_libs: [
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index df24b10..5c9c8b6 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -61,6 +61,21 @@
* decompressImage().
*/
size_t getDecompressedImageHeight();
+ /*
+ * Returns the XMP data from the image.
+ */
+ void* getXMPPtr();
+ /*
+ * Returns the decompressed XMP buffer size. This method must be called only after
+ * calling decompressImage().
+ */
+ size_t getXMPSize();
+
+ bool getCompressedImageParameters(const void* image, int length,
+ size_t* pWidth, size_t* pHeight,
+ std::vector<uint8_t>* &iccData,
+ std::vector<uint8_t>* &exifData);
+
private:
bool decode(const void* image, int length);
// Returns false if errors occur.
@@ -72,6 +87,9 @@
static const int kCompressBatchSize = 16;
// The buffer that holds the decompressed result.
std::vector<JOCTET> mResultBuffer;
+ // The buffer that holds XMP Data.
+ std::vector<JOCTET> mXMPBuffer;
+
// Resolution of the decompressed image.
size_t mWidth;
size_t mHeight;
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 10a9db8..5597303 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -34,6 +34,13 @@
JPEGR_TF_PQ = 1,
} jpegr_transfer_function;
+struct jpegr_info_struct {
+ size_t width;
+ size_t height;
+ std::vector<uint8_t>* iccData;
+ std::vector<uint8_t>* exifData;
+};
+
/*
* Holds information for uncompressed image or recovery map.
*/
@@ -117,6 +124,7 @@
typedef struct jpegr_compressed_struct* jr_compressed_ptr;
typedef struct jpegr_exif_struct* jr_exif_ptr;
typedef struct jpegr_metadata* jr_metadata_ptr;
+typedef struct jpegr_info_struct* jr_info_ptr;
class RecoveryMap {
public:
@@ -200,8 +208,19 @@
*/
status_t decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
jr_uncompressed_ptr dest,
- jr_exif_ptr exif,
- bool request_sdr);
+ jr_exif_ptr exif = nullptr,
+ bool request_sdr = false);
+
+ /*
+ * Gets Info from JPEGR file without decoding it.
+ *
+ * The output is filled jpegr_info structure
+ * @param compressed_jpegr_image compressed JPEGR image
+ * @param jpegr_info pointer to output JPEGR info
+ * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+ */
+ status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
+ jr_info_ptr jpegr_info);
private:
/*
* This method is called in the decoding pipeline. It will decode the recovery map.
@@ -260,17 +279,26 @@
jr_uncompressed_ptr dest);
/*
+ * This methoud is called to separate primary image and recovery map image from JPEGR
+ *
+ * @param compressed_jpegr_image compressed JPEGR image
+ * @param primary_image destination of primary image
+ * @param recovery_map destination of compressed recovery map
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+ jr_compressed_ptr primary_image,
+ jr_compressed_ptr recovery_map);
+ /*
* This method is called in the decoding pipeline. It will read XMP metadata to find the start
* position of the compressed recovery map, and will extract the compressed recovery map.
*
* @param compressed_jpegr_image compressed JPEGR image
* @param dest destination of compressed recovery map
- * @param metadata destination of the recovery map metadata
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
- jr_compressed_ptr dest,
- jr_metadata_ptr metadata);
+ jr_compressed_ptr dest);
/*
* This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
new file mode 100644
index 0000000..e35f2d7
--- /dev/null
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -0,0 +1,40 @@
+/*
+ * 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_RECOVERYMAPUTILS_H
+#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
+
+#include <stdint.h>
+#include <cstdio>
+
+
+namespace android::recoverymap {
+
+struct jpegr_metadata;
+
+/*
+ * Parses XMP packet and fills metadata with data from XMP
+ *
+ * @param xmp_data pointer to XMP packet
+ * @param xmp_size size of XMP packet
+ * @param metadata place to store HDR metadata values
+ * @return true if metadata is successfully retrieved, false otherwise
+*/
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);
+
+}
+
+#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
\ No newline at end of file
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index c1fb6c3..0185e55 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -20,8 +20,15 @@
#include <errno.h>
#include <setjmp.h>
+#include <string>
+
+using namespace std;
namespace android::recoverymap {
+
+const uint32_t kExifMarker = JPEG_APP0 + 1;
+const uint32_t kICCMarker = JPEG_APP0 + 2;
+
struct jpegr_source_mgr : jpeg_source_mgr {
jpegr_source_mgr(const uint8_t* ptr, int len);
~jpegr_source_mgr();
@@ -88,6 +95,7 @@
}
mResultBuffer.clear();
+ mXMPBuffer.clear();
if (!decode(image, length)) {
return false;
}
@@ -103,6 +111,15 @@
return mResultBuffer.size();
}
+void* JpegDecoder::getXMPPtr() {
+ return mXMPBuffer.data();
+}
+
+size_t JpegDecoder::getXMPSize() {
+ return mXMPBuffer.size();
+}
+
+
size_t JpegDecoder::getDecompressedImageWidth() {
return mWidth;
}
@@ -115,6 +132,8 @@
jpeg_decompress_struct cinfo;
jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
jpegrerror_mgr myerr;
+ string nameSpace = "http://ns.adobe.com/xap/1.0/";
+
cinfo.err = jpeg_std_error(&myerr.pub);
myerr.pub.error_exit = jpegrerror_exit;
@@ -124,9 +143,26 @@
}
jpeg_create_decompress(&cinfo);
+ jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+
cinfo.src = &mgr;
jpeg_read_header(&cinfo, TRUE);
+ // Save XMP Data
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
+ if (marker->marker == kExifMarker) {
+ const unsigned int len = marker->data_length;
+ if (len > nameSpace.size() &&
+ !strncmp(reinterpret_cast<const char*>(marker->data),
+ nameSpace.c_str(), nameSpace.size())) {
+ mXMPBuffer.resize(len+1, 0);
+ memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+ break;
+ }
+ }
+ }
+
+
mWidth = cinfo.image_width;
mHeight = cinfo.image_height;
@@ -161,6 +197,43 @@
return decompressYUV(cinfo, dest);
}
+bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
+ size_t *pWidth, size_t *pHeight,
+ std::vector<uint8_t> *&iccData , std::vector<uint8_t> *&exifData) {
+ jpeg_decompress_struct cinfo;
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ jpegrerror_mgr myerr;
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
+
+ cinfo.src = &mgr;
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
+ *pWidth = cinfo.image_width;
+ *pHeight = cinfo.image_height;
+
+ //TODO: Parse iccProfile and exifData
+ (void)iccData;
+ (void)exifData;
+
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
+
bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
JSAMPROW y[kCompressBatchSize];
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index ad41839..b685294 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -18,15 +18,23 @@
#include <jpegrecoverymap/jpegencoder.h>
#include <jpegrecoverymap/jpegdecoder.h>
#include <jpegrecoverymap/recoverymapmath.h>
+#include <jpegrecoverymap/recoverymaputils.h>
#include <image_io/jpeg/jpeg_marker.h>
#include <image_io/xml/xml_writer.h>
+#include <image_io/jpeg/jpeg_info.h>
+#include <image_io/jpeg/jpeg_scanner.h>
+#include <image_io/jpeg/jpeg_info_builder.h>
+#include <image_io/base/data_segment_data_source.h>
+#include <utils/Log.h>
#include <memory>
#include <sstream>
#include <string>
+#include <cmath>
using namespace std;
+using namespace photos_editing_formats::image_io;
namespace android::recoverymap {
@@ -234,17 +242,41 @@
return NO_ERROR;
}
+status_t RecoveryMap::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;
+ }
+
+ jpegr_compressed_struct primary_image, recovery_map;
+ JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
+ &primary_image, &recovery_map));
+
+ JpegDecoder jpeg_decoder;
+ if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
+ &jpegr_info->width, &jpegr_info->height,
+ jpegr_info->iccData, jpegr_info->exifData)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
jr_uncompressed_ptr dest,
- jr_exif_ptr /* exif */,
- bool /* request_sdr */) {
+ jr_exif_ptr exif,
+ bool request_sdr) {
if (compressed_jpegr_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
+ // TODO: fill EXIF data
+ (void) exif;
+
jpegr_compressed_struct compressed_map;
jpegr_metadata metadata;
- JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map, &metadata));
+ JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
jpegr_uncompressed_struct map;
JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
@@ -259,7 +291,19 @@
uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
- JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
+ if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
+ jpeg_decoder.getXMPSize(), &metadata)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ if (request_sdr) {
+ memcpy(dest->data, uncompressed_yuv_420_image.data,
+ uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
+ dest->width = uncompressed_yuv_420_image.width;
+ dest->height = uncompressed_yuv_420_image.height;
+ } else {
+ JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
+ }
return NO_ERROR;
}
@@ -467,19 +511,66 @@
reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
}
}
+ return NO_ERROR;
+}
+
+status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
+ jr_compressed_ptr primary_image,
+ jr_compressed_ptr recovery_map) {
+ if (compressed_jpegr_image == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ MessageHandler msg_handler;
+ std::shared_ptr<DataSegment> seg =
+ DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
+ static_cast<const uint8_t*>(compressed_jpegr_image->data),
+ DataSegment::BufferDispositionPolicy::kDontDelete);
+ DataSegmentDataSource data_source(seg);
+ JpegInfoBuilder jpeg_info_builder;
+ jpeg_info_builder.SetImageLimit(2);
+ JpegScanner jpeg_scanner(&msg_handler);
+ jpeg_scanner.Run(&data_source, &jpeg_info_builder);
+ data_source.Reset();
+
+ if (jpeg_scanner.HasError()) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ const auto& jpeg_info = jpeg_info_builder.GetInfo();
+ const auto& image_ranges = jpeg_info.GetImageRanges();
+ if (image_ranges.empty()) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (image_ranges.size() != 2) {
+ // Must be 2 JPEG Images
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (primary_image != nullptr) {
+ primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+ image_ranges[0].GetBegin();
+ primary_image->length = image_ranges[0].GetLength();
+ }
+
+ if (recovery_map != nullptr) {
+ recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
+ image_ranges[1].GetBegin();
+ recovery_map->length = image_ranges[1].GetLength();
+ }
return NO_ERROR;
}
+
status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
- jr_compressed_ptr dest,
- jr_metadata_ptr metadata) {
- if (compressed_jpegr_image == nullptr || dest == nullptr || metadata == nullptr) {
+ jr_compressed_ptr dest) {
+ if (compressed_jpegr_image == nullptr || dest == nullptr) {
return ERROR_JPEGR_INVALID_NULL_PTR;
}
- // TBD
- return NO_ERROR;
+ return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
}
status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
new file mode 100644
index 0000000..f8549f1
--- /dev/null
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#include <jpegrecoverymap/recoverymaputils.h>
+#include <jpegrecoverymap/recoverymap.h>
+
+namespace android::recoverymap {
+
+bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
+ // TODO: Parse XMP Data
+ (void)xmp_data;
+ (void)xmp_size;
+ metadata->rangeScalingFactor = 0.0708864;
+ metadata->transferFunction = JPEGR_TF_HLG;
+ return true;
+}
+
+}
\ No newline at end of file