diff options
author | 2022-11-16 01:40:53 +0000 | |
---|---|---|
committer | 2022-11-22 20:12:16 +0000 | |
commit | 1dcc44292641990726279fdd51ce8072993a38bc (patch) | |
tree | 5f8af69df30a684ec8ffde703d4104181a7c37e9 | |
parent | 83affa699122680be1b833a8e80bcc5828e83bd0 (diff) |
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
-rw-r--r-- | libs/jpegrecoverymap/Android.bp | 1 | ||||
-rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h | 18 | ||||
-rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h | 38 | ||||
-rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h | 40 | ||||
-rw-r--r-- | libs/jpegrecoverymap/jpegdecoder.cpp | 73 | ||||
-rw-r--r-- | libs/jpegrecoverymap/recoverymap.cpp | 109 | ||||
-rw-r--r-- | libs/jpegrecoverymap/recoverymaputils.cpp | 31 |
7 files changed, 296 insertions, 14 deletions
diff --git a/libs/jpegrecoverymap/Android.bp b/libs/jpegrecoverymap/Android.bp index 0375915839..54af7c912d 100644 --- a/libs/jpegrecoverymap/Android.bp +++ b/libs/jpegrecoverymap/Android.bp @@ -31,6 +31,7 @@ cc_library_static { 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 df24b10ebc..5c9c8b6ec6 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -61,6 +61,21 @@ public: * 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 @@ private: 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 10a9db879d..55973034bb 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -34,6 +34,13 @@ typedef enum { 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_uncompressed_struct* jr_uncompressed_ptr; 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 @@ public: */ 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 @@ private: 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 0000000000..e35f2d72cb --- /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 c1fb6c3f1d..0185e55e9e 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 @@ bool JpegDecoder::decompressImage(const void* image, int length) { } mResultBuffer.clear(); + mXMPBuffer.clear(); if (!decode(image, length)) { return false; } @@ -103,6 +111,15 @@ size_t JpegDecoder::getDecompressedImageSize() { 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 @@ bool JpegDecoder::decode(const void* image, int length) { 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 @@ bool JpegDecoder::decode(const void* image, int length) { } 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 @@ bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, 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 ad41839601..b685294dcc 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 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, 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 @@ status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, 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 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_ 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 0000000000..f8549f1f9d --- /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 |