blob: 91d6181fd4f620f2f372ce77e6be9017baab3393 [file] [log] [blame]
/*
* 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.
*/
#ifdef _WIN32
#include <windows.h>
#include <sysinfoapi.h>
#else
#include <unistd.h>
#endif
#include <condition_variable>
#include <deque>
#include <functional>
#include <mutex>
#include <thread>
#include "ultrahdr/editorhelper.h"
#include "ultrahdr/gainmapmetadata.h"
#include "ultrahdr/ultrahdrcommon.h"
#include "ultrahdr/jpegr.h"
#include "ultrahdr/icc.h"
#include "ultrahdr/multipictureformat.h"
#include "image_io/base/data_segment_data_source.h"
#include "image_io/jpeg/jpeg_info.h"
#include "image_io/jpeg/jpeg_info_builder.h"
#include "image_io/jpeg/jpeg_marker.h"
#include "image_io/jpeg/jpeg_scanner.h"
using namespace std;
using namespace photos_editing_formats::image_io;
namespace ultrahdr {
#ifdef UHDR_ENABLE_GLES
uhdr_error_info_t applyGainMapGLES(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img,
uhdr_gainmap_metadata_ext_t* gainmap_metadata,
uhdr_color_transfer_t output_ct, float display_boost,
uhdr_raw_image_t* dest, uhdr_opengl_ctxt_t* opengl_ctxt);
#endif
// Gain map metadata
static const bool kWriteXmpMetadata = true;
static const bool kWriteIso21496_1Metadata = false;
static const string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
static const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1";
static_assert(kWriteXmpMetadata || kWriteIso21496_1Metadata,
"Must write gain map metadata in XMP format, or iso 21496-1 format, or both.");
class JobQueue {
public:
bool dequeueJob(unsigned int& rowStart, unsigned int& rowEnd);
void enqueueJob(unsigned int rowStart, unsigned int rowEnd);
void markQueueForEnd();
void reset();
private:
bool mQueuedAllJobs = false;
std::deque<std::tuple<unsigned int, unsigned int>> mJobs;
std::mutex mMutex;
std::condition_variable mCv;
};
bool JobQueue::dequeueJob(unsigned int& rowStart, unsigned int& rowEnd) {
std::unique_lock<std::mutex> lock{mMutex};
while (true) {
if (mJobs.empty()) {
if (mQueuedAllJobs) {
return false;
} else {
mCv.wait_for(lock, std::chrono::milliseconds(100));
}
} else {
auto it = mJobs.begin();
rowStart = std::get<0>(*it);
rowEnd = std::get<1>(*it);
mJobs.erase(it);
return true;
}
}
return false;
}
void JobQueue::enqueueJob(unsigned int rowStart, unsigned int rowEnd) {
std::unique_lock<std::mutex> lock{mMutex};
mJobs.push_back(std::make_tuple(rowStart, rowEnd));
lock.unlock();
mCv.notify_one();
}
void JobQueue::markQueueForEnd() {
std::unique_lock<std::mutex> lock{mMutex};
mQueuedAllJobs = true;
lock.unlock();
mCv.notify_all();
}
void JobQueue::reset() {
std::unique_lock<std::mutex> lock{mMutex};
mJobs.clear();
mQueuedAllJobs = false;
}
/*
* MessageWriter implementation for ALOG functions.
*/
class AlogMessageWriter : public MessageWriter {
public:
void WriteMessage(const Message& message) override {
std::string log = GetFormattedMessage(message);
ALOGD("%s", log.c_str());
}
};
unsigned int GetCPUCoreCount() { return (std::max)(1u, std::thread::hardware_concurrency()); }
JpegR::JpegR(void* uhdrGLESCtxt, int mapDimensionScaleFactor, int mapCompressQuality,
bool useMultiChannelGainMap, float gamma, uhdr_enc_preset_t preset,
float minContentBoost, float maxContentBoost, float targetDispPeakBrightness) {
mUhdrGLESCtxt = uhdrGLESCtxt;
mMapDimensionScaleFactor = mapDimensionScaleFactor;
mMapCompressQuality = mapCompressQuality;
mUseMultiChannelGainMap = useMultiChannelGainMap;
mGamma = gamma;
mEncPreset = preset;
mMinContentBoost = minContentBoost;
mMaxContentBoost = maxContentBoost;
mTargetDispPeakBrightness = targetDispPeakBrightness;
}
/*
* Helper function copies the JPEG image from without EXIF.
*
* @param pDest destination of the data to be written.
* @param pSource source of data being written.
* @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
* (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
* @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
*/
static void copyJpegWithoutExif(uhdr_compressed_image_t* pDest, uhdr_compressed_image_t* pSource,
size_t exif_pos, size_t exif_size) {
const size_t exif_offset = 4; // exif_pos has 4 bytes offset to the FF sign
pDest->data_sz = pSource->data_sz - exif_size - exif_offset;
pDest->data = new uint8_t[pDest->data_sz];
pDest->capacity = pDest->data_sz;
pDest->cg = pSource->cg;
pDest->ct = pSource->ct;
pDest->range = pSource->range;
memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
(uint8_t*)pSource->data + exif_pos + exif_size, pSource->data_sz - exif_pos - exif_size);
}
/* Encode API-0 */
uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest,
int quality, uhdr_mem_block_t* exif) {
uhdr_img_fmt_t sdr_intent_fmt;
if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
sdr_intent_fmt = UHDR_IMG_FMT_12bppYCbCr420;
} else if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
sdr_intent_fmt = UHDR_IMG_FMT_24bppYCbCr444;
} else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
sdr_intent_fmt = UHDR_IMG_FMT_32bppRGBA8888;
} else {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "unsupported hdr intent color format %d",
hdr_intent->fmt);
return status;
}
std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent = std::make_unique<uhdr_raw_image_ext_t>(
sdr_intent_fmt, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, hdr_intent->w,
hdr_intent->h, 64);
// tone map
UHDR_ERR_CHECK(toneMap(hdr_intent, sdr_intent.get()));
// If hdr intent is tonemapped internally, it is observed from quality pov,
// generateGainMapOnePass() is sufficient
mEncPreset = UHDR_USAGE_REALTIME; // overriding the config option
// generate gain map
uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
UHDR_ERR_CHECK(generateGainMap(sdr_intent.get(), hdr_intent, &metadata, gainmap,
/* sdr_is_601 */ false,
/* use_luminance */ false));
// compress gain map
JpegEncoderHelper jpeg_enc_obj_gm;
UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg);
// compress sdr image
std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext;
uhdr_raw_image_t* sdr_intent_yuv = sdr_intent.get();
if (isPixelFormatRgb(sdr_intent->fmt)) {
sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent.get());
sdr_intent_yuv = sdr_intent_yuv_ext.get();
}
JpegEncoderHelper jpeg_enc_obj_sdr;
UHDR_ERR_CHECK(
jpeg_enc_obj_sdr.compressImage(sdr_intent_yuv, quality, icc->getData(), icc->getLength()));
uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage();
sdr_intent_compressed.cg = sdr_intent_yuv->cg;
// append gain map, no ICC since JPEG encode already did it
UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr,
/* icc size */ 0, &metadata, dest));
return g_no_error;
}
/* Encode API-1 */
uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
uhdr_compressed_image_t* dest, int quality,
uhdr_mem_block_t* exif) {
// generate gain map
uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap));
// compress gain map
JpegEncoderHelper jpeg_enc_obj_gm;
UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg);
std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext;
uhdr_raw_image_t* sdr_intent_yuv = sdr_intent;
if (isPixelFormatRgb(sdr_intent->fmt)) {
sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent);
sdr_intent_yuv = sdr_intent_yuv_ext.get();
}
// convert to bt601 YUV encoding for JPEG encode
#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
UHDR_ERR_CHECK(convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3));
#else
UHDR_ERR_CHECK(convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3));
#endif
// compress sdr image
JpegEncoderHelper jpeg_enc_obj_sdr;
UHDR_ERR_CHECK(
jpeg_enc_obj_sdr.compressImage(sdr_intent_yuv, quality, icc->getData(), icc->getLength()));
uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage();
sdr_intent_compressed.cg = sdr_intent_yuv->cg;
// append gain map, no ICC since JPEG encode already did it
UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr,
/* icc size */ 0, &metadata, dest));
return g_no_error;
}
/* Encode API-2 */
uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent,
uhdr_compressed_image_t* sdr_intent_compressed,
uhdr_compressed_image_t* dest) {
JpegDecoderHelper jpeg_dec_obj_sdr;
UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data,
sdr_intent_compressed->data_sz, PARSE_STREAM));
if (hdr_intent->w != jpeg_dec_obj_sdr.getDecompressedImageWidth() ||
hdr_intent->h != jpeg_dec_obj_sdr.getDecompressedImageHeight()) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(
status.detail, sizeof status.detail,
"sdr intent resolution %dx%d and compressed image sdr intent resolution %dx%d do not match",
sdr_intent->w, sdr_intent->h, (int)jpeg_dec_obj_sdr.getDecompressedImageWidth(),
(int)jpeg_dec_obj_sdr.getDecompressedImageHeight());
return status;
}
// generate gain map
uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap));
// compress gain map
JpegEncoderHelper jpeg_enc_obj_gm;
UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest);
}
/* Encode API-3 */
uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent,
uhdr_compressed_image_t* sdr_intent_compressed,
uhdr_compressed_image_t* dest) {
// decode input jpeg, gamut is going to be bt601.
JpegDecoderHelper jpeg_dec_obj_sdr;
UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data,
sdr_intent_compressed->data_sz));
uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage();
if (jpeg_dec_obj_sdr.getICCSize() > 0) {
uhdr_color_gamut_t cg =
IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize());
if (cg == UHDR_CG_UNSPECIFIED ||
(sdr_intent_compressed->cg != UHDR_CG_UNSPECIFIED && sdr_intent_compressed->cg != cg)) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"configured color gamut %d does not match with color gamut specified in icc box %d",
sdr_intent_compressed->cg, cg);
return status;
}
sdr_intent.cg = cg;
} else {
if (sdr_intent_compressed->cg <= UHDR_CG_UNSPECIFIED ||
sdr_intent_compressed->cg > UHDR_CG_BT_2100) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d",
sdr_intent_compressed->cg);
return status;
}
sdr_intent.cg = sdr_intent_compressed->cg;
}
if (hdr_intent->w != sdr_intent.w || hdr_intent->h != sdr_intent.h) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"sdr intent resolution %dx%d and hdr intent resolution %dx%d do not match",
sdr_intent.w, sdr_intent.h, hdr_intent->w, hdr_intent->h);
return status;
}
// generate gain map
uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion);
std::unique_ptr<uhdr_raw_image_ext_t> gainmap;
UHDR_ERR_CHECK(
generateGainMap(&sdr_intent, hdr_intent, &metadata, gainmap, true /* sdr_is_601 */));
// compress gain map
JpegEncoderHelper jpeg_enc_obj_gm;
UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm));
uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage();
return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest);
}
/* Encode API-4 */
uhdr_error_info_t JpegR::encodeJPEGR(uhdr_compressed_image_t* base_img_compressed,
uhdr_compressed_image_t* gainmap_img_compressed,
uhdr_gainmap_metadata_ext_t* metadata,
uhdr_compressed_image_t* dest) {
// We just want to check if ICC is present, so don't do a full decode. Note,
// this doesn't verify that the ICC is valid.
JpegDecoderHelper decoder;
UHDR_ERR_CHECK(decoder.parseImage(base_img_compressed->data, base_img_compressed->data_sz));
// Add ICC if not already present.
if (decoder.getICCSize() > 0) {
UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr,
/* icc */ nullptr, /* icc size */ 0, metadata, dest));
} else {
if (base_img_compressed->cg <= UHDR_CG_UNSPECIFIED ||
base_img_compressed->cg > UHDR_CG_BT_2100) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d",
base_img_compressed->cg);
return status;
}
std::shared_ptr<DataStruct> newIcc =
IccHelper::writeIccProfile(UHDR_CT_SRGB, base_img_compressed->cg);
UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr,
newIcc->getData(), newIcc->getLength(), metadata, dest));
}
return g_no_error;
}
uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding,
uhdr_color_gamut_t dst_encoding) {
const std::array<float, 9>* coeffs_ptr = nullptr;
uhdr_error_info_t status = g_no_error;
switch (src_encoding) {
case UHDR_CG_BT_709:
switch (dst_encoding) {
case UHDR_CG_BT_709:
return status;
case UHDR_CG_DISPLAY_P3:
coeffs_ptr = &kYuvBt709ToBt601;
break;
case UHDR_CG_BT_2100:
coeffs_ptr = &kYuvBt709ToBt2100;
break;
default:
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d",
dst_encoding);
return status;
}
break;
case UHDR_CG_DISPLAY_P3:
switch (dst_encoding) {
case UHDR_CG_BT_709:
coeffs_ptr = &kYuvBt601ToBt709;
break;
case UHDR_CG_DISPLAY_P3:
return status;
case UHDR_CG_BT_2100:
coeffs_ptr = &kYuvBt601ToBt2100;
break;
default:
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d",
dst_encoding);
return status;
}
break;
case UHDR_CG_BT_2100:
switch (dst_encoding) {
case UHDR_CG_BT_709:
coeffs_ptr = &kYuvBt2100ToBt709;
break;
case UHDR_CG_DISPLAY_P3:
coeffs_ptr = &kYuvBt2100ToBt601;
break;
case UHDR_CG_BT_2100:
return status;
default:
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d",
dst_encoding);
return status;
}
break;
default:
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "Unrecognized src color gamut %d",
src_encoding);
return status;
}
if (image->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
transformYuv420(image, *coeffs_ptr);
} else if (image->fmt == UHDR_IMG_FMT_24bppYCbCr444) {
transformYuv444(image, *coeffs_ptr);
} else {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for performing gamut conversion for color format %d",
image->fmt);
return status;
}
return status;
}
uhdr_error_info_t JpegR::compressGainMap(uhdr_raw_image_t* gainmap_img,
JpegEncoderHelper* jpeg_enc_obj) {
return jpeg_enc_obj->compressImage(gainmap_img, mMapCompressQuality, nullptr, 0);
}
uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* hdr_intent,
uhdr_gainmap_metadata_ext_t* gainmap_metadata,
std::unique_ptr<uhdr_raw_image_ext_t>& gainmap_img,
bool sdr_is_601, bool use_luminance) {
uhdr_error_info_t status = g_no_error;
if (sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444 &&
sdr_intent->fmt != UHDR_IMG_FMT_16bppYCbCr422 &&
sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420 &&
sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"generate gainmap method expects sdr intent color format to be one of "
"{UHDR_IMG_FMT_24bppYCbCr444, UHDR_IMG_FMT_16bppYCbCr422, "
"UHDR_IMG_FMT_12bppYCbCr420, UHDR_IMG_FMT_32bppRGBA8888}. Received %d",
sdr_intent->fmt);
return status;
}
if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 &&
hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"generate gainmap method expects hdr intent color format to be one of "
"{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, "
"UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d",
hdr_intent->fmt);
return status;
}
/*if (mUseMultiChannelGainMap) {
if (!kWriteIso21496_1Metadata || kWriteXmpMetadata) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"Multi-channel gain map is only supported for ISO 21496-1 metadata");
return status;
}
}*/
ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct);
if (hdrInvOetf == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for converting transfer characteristics %d to linear",
hdr_intent->ct);
return status;
}
LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg);
if (hdrLuminanceFn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for calculating luminance for color gamut %d",
hdr_intent->cg);
return status;
}
SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct);
if (hdrOotfFn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for calculating Ootf for color transfer %d",
hdr_intent->ct);
return status;
}
float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct);
if (hdr_white_nits == -1.0f) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"received invalid peak brightness %f nits for hdr reference display with color "
"transfer %d ",
hdr_white_nits, hdr_intent->ct);
return status;
}
ColorTransformFn hdrGamutConversionFn = getGamutConversionFn(sdr_intent->cg, hdr_intent->cg);
if (hdrGamutConversionFn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for gamut conversion from %d to %d", hdr_intent->cg,
sdr_intent->cg);
return status;
}
ColorTransformFn sdrYuvToRgbFn = getYuvToRgbFn(sdr_intent->cg);
if (sdrYuvToRgbFn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for converting yuv to rgb for color gamut %d",
sdr_intent->cg);
return status;
}
ColorTransformFn hdrYuvToRgbFn = getYuvToRgbFn(hdr_intent->cg);
if (hdrYuvToRgbFn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for converting yuv to rgb for color gamut %d",
hdr_intent->cg);
return status;
}
LuminanceFn luminanceFn = getLuminanceFn(sdr_intent->cg);
if (luminanceFn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for computing luminance for color gamut %d",
sdr_intent->cg);
return status;
}
SamplePixelFn sdr_sample_pixel_fn = getSamplePixelFn(sdr_intent->fmt);
if (sdr_sample_pixel_fn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for reading pixels for color format %d", sdr_intent->fmt);
return status;
}
SamplePixelFn hdr_sample_pixel_fn = getSamplePixelFn(hdr_intent->fmt);
if (hdr_sample_pixel_fn == nullptr) {
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for reading pixels for color format %d", hdr_intent->fmt);
return status;
}
if (sdr_is_601) {
sdrYuvToRgbFn = p3YuvToRgb;
}
unsigned int image_width = sdr_intent->w;
unsigned int image_height = sdr_intent->h;
unsigned int map_width = image_width / mMapDimensionScaleFactor;
unsigned int map_height = image_height / mMapDimensionScaleFactor;
if (map_width == 0 || map_height == 0) {
int scaleFactor = (std::min)(image_width, image_height);
scaleFactor = (scaleFactor >= DCTSIZE) ? (scaleFactor / DCTSIZE) : 1;
ALOGW(
"configured gainmap scale factor is resulting in gainmap width and/or height to be zero, "
"image width %u, image height %u, scale factor %d. Modifying gainmap scale factor to %d ",
image_width, image_height, mMapDimensionScaleFactor, scaleFactor);
setMapDimensionScaleFactor(scaleFactor);
map_width = image_width / mMapDimensionScaleFactor;
map_height = image_height / mMapDimensionScaleFactor;
}
gainmap_img = std::make_unique<uhdr_raw_image_ext_t>(
mUseMultiChannelGainMap ? UHDR_IMG_FMT_24bppRGB888 : UHDR_IMG_FMT_8bppYCbCr400,
UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, map_width, map_height, 64);
uhdr_raw_image_ext_t* dest = gainmap_img.get();
auto generateGainMapOnePass = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_height,
hdrInvOetf, hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn,
luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn,
hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void {
gainmap_metadata->max_content_boost = hdr_white_nits / kSdrWhiteNits;
gainmap_metadata->min_content_boost = 1.0f;
gainmap_metadata->gamma = mGamma;
gainmap_metadata->offset_sdr = 0.0f;
gainmap_metadata->offset_hdr = 0.0f;
gainmap_metadata->hdr_capacity_min = 1.0f;
if (this->mTargetDispPeakBrightness != -1.0f) {
gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits;
} else {
gainmap_metadata->hdr_capacity_max = gainmap_metadata->max_content_boost;
}
float log2MinBoost = log2(gainmap_metadata->min_content_boost);
float log2MaxBoost = log2(gainmap_metadata->max_content_boost);
const int threads = (std::min)(GetCPUCoreCount(), 4u);
const int jobSizeInRows = 1;
unsigned int rowStep = threads == 1 ? map_height : jobSizeInRows;
JobQueue jobQueue;
std::function<void()> generateMap =
[this, sdr_intent, hdr_intent, gainmap_metadata, dest, hdrInvOetf, hdrLuminanceFn,
hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn,
sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, log2MinBoost, log2MaxBoost,
use_luminance, &jobQueue]() -> void {
unsigned int rowStart, rowEnd;
const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
const float hdrSampleToNitsFactor =
hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < dest->w; ++x) {
Color sdr_rgb_gamma;
if (isSdrIntentRgb) {
sdr_rgb_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
} else {
Color sdr_yuv_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
}
// We are assuming the SDR input is always sRGB transfer.
#if USE_SRGB_INVOETF_LUT
Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
#else
Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
#endif
Color hdr_rgb_gamma;
if (isHdrIntentRgb) {
hdr_rgb_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
} else {
Color hdr_yuv_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
}
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
if (mUseMultiChannelGainMap) {
Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor;
size_t pixel_idx = (x + y * dest->stride[UHDR_PLANE_PACKED]) * 3;
reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = encodeGain(
sdr_rgb_nits.r, hdr_rgb_nits.r, gainmap_metadata, log2MinBoost, log2MaxBoost);
reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 1] =
encodeGain(sdr_rgb_nits.g, hdr_rgb_nits.g, gainmap_metadata, log2MinBoost,
log2MaxBoost);
reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 2] =
encodeGain(sdr_rgb_nits.b, hdr_rgb_nits.b, gainmap_metadata, log2MinBoost,
log2MaxBoost);
} else {
float sdr_y_nits;
float hdr_y_nits;
if (use_luminance) {
sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor;
} else {
sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits;
hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor;
}
size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_Y];
reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_Y])[pixel_idx] =
encodeGain(sdr_y_nits, hdr_y_nits, gainmap_metadata, log2MinBoost, log2MaxBoost);
}
}
}
}
};
// generate map
std::vector<std::thread> workers;
for (int th = 0; th < threads - 1; th++) {
workers.push_back(std::thread(generateMap));
}
for (unsigned int rowStart = 0; rowStart < map_height;) {
unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height);
jobQueue.enqueueJob(rowStart, rowEnd);
rowStart = rowEnd;
}
jobQueue.markQueueForEnd();
generateMap();
std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
};
auto generateGainMapTwoPass =
[this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_width, map_height, hdrInvOetf,
hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn,
sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void {
uhdr_memory_block_t gainmap_mem((size_t)map_width * map_height * sizeof(float) *
(mUseMultiChannelGainMap ? 3 : 1));
float* gainmap_data = reinterpret_cast<float*>(gainmap_mem.m_buffer.get());
float gainmap_min[3] = {127.0f, 127.0f, 127.0f};
float gainmap_max[3] = {-128.0f, -128.0f, -128.0f};
std::mutex gainmap_minmax;
const int threads = (std::min)(GetCPUCoreCount(), 4u);
const int jobSizeInRows = 1;
unsigned int rowStep = threads == 1 ? map_height : jobSizeInRows;
JobQueue jobQueue;
std::function<void()> generateMap =
[this, sdr_intent, hdr_intent, gainmap_data, map_width, hdrInvOetf, hdrLuminanceFn,
hdrOotfFn, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn,
sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance, &gainmap_min,
&gainmap_max, &gainmap_minmax, &jobQueue]() -> void {
unsigned int rowStart, rowEnd;
const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
const float hdrSampleToNitsFactor =
hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
float gainmap_min_th[3] = {127.0f, 127.0f, 127.0f};
float gainmap_max_th[3] = {-128.0f, -128.0f, -128.0f};
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < map_width; ++x) {
Color sdr_rgb_gamma;
if (isSdrIntentRgb) {
sdr_rgb_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
} else {
Color sdr_yuv_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y);
sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
}
// We are assuming the SDR input is always sRGB transfer.
#if USE_SRGB_INVOETF_LUT
Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
#else
Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
#endif
Color hdr_rgb_gamma;
if (isHdrIntentRgb) {
hdr_rgb_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
} else {
Color hdr_yuv_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y);
hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
}
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
hdr_rgb = hdrGamutConversionFn(hdr_rgb);
if (mUseMultiChannelGainMap) {
Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor;
size_t pixel_idx = (x + y * map_width) * 3;
gainmap_data[pixel_idx] = computeGain(sdr_rgb_nits.r, hdr_rgb_nits.r);
gainmap_data[pixel_idx + 1] = computeGain(sdr_rgb_nits.g, hdr_rgb_nits.g);
gainmap_data[pixel_idx + 2] = computeGain(sdr_rgb_nits.b, hdr_rgb_nits.b);
for (int i = 0; i < 3; i++) {
gainmap_min_th[i] = (std::min)(gainmap_data[pixel_idx + i], gainmap_min_th[i]);
gainmap_max_th[i] = (std::max)(gainmap_data[pixel_idx + i], gainmap_max_th[i]);
}
} else {
float sdr_y_nits;
float hdr_y_nits;
if (use_luminance) {
sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor;
} else {
sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits;
hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor;
}
size_t pixel_idx = x + y * map_width;
gainmap_data[pixel_idx] = computeGain(sdr_y_nits, hdr_y_nits);
gainmap_min_th[0] = (std::min)(gainmap_data[pixel_idx], gainmap_min_th[0]);
gainmap_max_th[0] = (std::max)(gainmap_data[pixel_idx], gainmap_max_th[0]);
}
}
}
}
{
std::unique_lock<std::mutex> lock{gainmap_minmax};
for (int index = 0; index < (mUseMultiChannelGainMap ? 3 : 1); index++) {
gainmap_min[index] = (std::min)(gainmap_min[index], gainmap_min_th[index]);
gainmap_max[index] = (std::max)(gainmap_max[index], gainmap_max_th[index]);
}
}
};
// generate map
std::vector<std::thread> workers;
for (int th = 0; th < threads - 1; th++) {
workers.push_back(std::thread(generateMap));
}
for (unsigned int rowStart = 0; rowStart < map_height;) {
unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height);
jobQueue.enqueueJob(rowStart, rowEnd);
rowStart = rowEnd;
}
jobQueue.markQueueForEnd();
generateMap();
std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
float min_content_boost_log2 = gainmap_min[0];
float max_content_boost_log2 = gainmap_max[0];
for (int index = 1; index < (mUseMultiChannelGainMap ? 3 : 1); index++) {
min_content_boost_log2 = (std::min)(gainmap_min[index], min_content_boost_log2);
max_content_boost_log2 = (std::max)(gainmap_max[index], max_content_boost_log2);
}
// -13.0 emphirically is a small enough gain factor that is capable of representing hdr
// black from any sdr luminance. Allowing further excursion might not offer any benefit and on
// the downside can cause bigger error during affine map and inverse map.
min_content_boost_log2 = (std::max)(-13.0f, min_content_boost_log2);
if (this->mMaxContentBoost != FLT_MAX) {
float suggestion = log2(this->mMaxContentBoost);
max_content_boost_log2 = (std::min)(max_content_boost_log2, suggestion);
}
if (this->mMinContentBoost != FLT_MIN) {
float suggestion = log2(this->mMinContentBoost);
min_content_boost_log2 = (std::max)(min_content_boost_log2, suggestion);
}
if (fabs(max_content_boost_log2 - min_content_boost_log2) < FLT_EPSILON) {
max_content_boost_log2 += 0.1; // to avoid div by zero during affine transform
}
std::function<void()> encodeMap = [this, gainmap_data, map_width, dest, min_content_boost_log2,
max_content_boost_log2, &jobQueue]() -> void {
unsigned int rowStart, rowEnd;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
if (mUseMultiChannelGainMap) {
for (size_t j = rowStart; j < rowEnd; j++) {
size_t dst_pixel_idx = j * dest->stride[UHDR_PLANE_PACKED] * 3;
size_t src_pixel_idx = j * map_width * 3;
for (size_t i = 0; i < map_width * 3; i++) {
reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[dst_pixel_idx + i] =
affineMapGain(gainmap_data[src_pixel_idx + i], min_content_boost_log2,
max_content_boost_log2, this->mGamma);
}
}
} else {
for (size_t j = rowStart; j < rowEnd; j++) {
size_t dst_pixel_idx = j * dest->stride[UHDR_PLANE_Y];
size_t src_pixel_idx = j * map_width;
for (size_t i = 0; i < map_width; i++) {
reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_Y])[dst_pixel_idx + i] =
affineMapGain(gainmap_data[src_pixel_idx + i], min_content_boost_log2,
max_content_boost_log2, this->mGamma);
}
}
}
}
};
workers.clear();
jobQueue.reset();
rowStep = threads == 1 ? map_height : 1;
for (int th = 0; th < threads - 1; th++) {
workers.push_back(std::thread(encodeMap));
}
for (unsigned int rowStart = 0; rowStart < map_height;) {
unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height);
jobQueue.enqueueJob(rowStart, rowEnd);
rowStart = rowEnd;
}
jobQueue.markQueueForEnd();
encodeMap();
std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
gainmap_metadata->max_content_boost = exp2(max_content_boost_log2);
gainmap_metadata->min_content_boost = exp2(min_content_boost_log2);
gainmap_metadata->gamma = this->mGamma;
gainmap_metadata->offset_sdr = 0.0f;
gainmap_metadata->offset_hdr = 0.0f;
gainmap_metadata->hdr_capacity_min = 1.0f;
if (this->mTargetDispPeakBrightness != -1.0f) {
gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits;
} else {
gainmap_metadata->hdr_capacity_max = hdr_white_nits / kSdrWhiteNits;
}
};
if (mEncPreset == UHDR_USAGE_REALTIME) {
generateGainMapOnePass();
} else {
generateGainMapTwoPass();
}
return status;
}
// JPEG/R structure:
// SOI (ff d8)
//
// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
// in the JPEG input (Encode API-2, API-3, API-4))
// APP1 (ff e1)
// 2 bytes of length (2 + length of exif package)
// EXIF package (this includes the first two bytes representing the package length)
//
// (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, ISO 21496-1 metadata, version only) APP2 (ff e2)
// 2 bytes of length
// name space (""urn:iso:std:iso:ts:21496:-1\0")
// 2 bytes minimum_version: (00 00)
// 2 bytes writer_version: (00 00)
//
// (Required, MPF package) APP2 (ff e2)
// 2 bytes of length
// MPF
//
// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
//
// 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, ISO 21496-1 metadata) APP2 (ff e2)
// 2 bytes of length
// name space (""urn:iso:std:iso:ts:21496:-1\0")
// metadata
//
// (Required) secondary image (the gain map, without the first two bytes (SOI))
//
// Metadata versions we are using:
// ECMA TR-98 for JFIF marker
// Exif 2.2 spec for EXIF marker
// Adobe XMP spec part 3 for XMP marker
// ICC v4.3 spec for ICC
uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compressed,
uhdr_compressed_image_t* gainmap_compressed,
uhdr_mem_block_t* pExif, void* pIcc, size_t icc_size,
uhdr_gainmap_metadata_ext_t* metadata,
uhdr_compressed_image_t* dest) {
const size_t xmpNameSpaceLength = kXmpNameSpace.size() + 1; // need to count the null terminator
const size_t isoNameSpaceLength = kIsoNameSpace.size() + 1; // need to count the null terminator
/////////////////////////////////////////////////////////////////////////////////////////////////
// calculate secondary image length first, because the length will be written into the primary //
// image xmp //
/////////////////////////////////////////////////////////////////////////////////////////////////
// XMP
const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
// xmp_secondary_length = 2 bytes representing the length of the package +
// + xmpNameSpaceLength = 29 bytes length
// + length of xmp packet = xmp_secondary.size()
const size_t xmp_secondary_length = 2 + xmpNameSpaceLength + xmp_secondary.size();
// ISO
uhdr_gainmap_metadata_frac iso_secondary_metadata;
std::vector<uint8_t> iso_secondary_data;
UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction(
metadata, &iso_secondary_metadata));
UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::encodeGainmapMetadata(&iso_secondary_metadata,
iso_secondary_data));
// iso_secondary_length = 2 bytes representing the length of the package +
// + isoNameSpaceLength = 28 bytes length
// + length of iso metadata packet = iso_secondary_data.size()
const size_t iso_secondary_length = 2 + isoNameSpaceLength + iso_secondary_data.size();
size_t secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + gainmap_compressed->data_sz;
if (kWriteXmpMetadata) {
secondary_image_size += xmp_secondary_length;
}
if (kWriteIso21496_1Metadata) {
secondary_image_size += iso_secondary_length;
}
// Check if EXIF package presents in the JPEG input.
// If so, extract and remove the EXIF package.
JpegDecoderHelper decoder;
UHDR_ERR_CHECK(decoder.parseImage(sdr_intent_compressed->data, sdr_intent_compressed->data_sz));
uhdr_mem_block_t exif_from_jpg;
exif_from_jpg.data = nullptr;
exif_from_jpg.data_sz = 0;
uhdr_compressed_image_t new_jpg_image;
new_jpg_image.data = nullptr;
new_jpg_image.data_sz = 0;
new_jpg_image.capacity = 0;
new_jpg_image.cg = UHDR_CG_UNSPECIFIED;
new_jpg_image.ct = UHDR_CT_UNSPECIFIED;
new_jpg_image.range = UHDR_CR_UNSPECIFIED;
std::unique_ptr<uint8_t[]> dest_data;
if (decoder.getEXIFPos() >= 0) {
if (pExif != nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"received exif from uhdr_enc_set_exif_data() while the base image intent already "
"contains exif, unsure which one to use");
return status;
}
copyJpegWithoutExif(&new_jpg_image, sdr_intent_compressed, decoder.getEXIFPos(),
decoder.getEXIFSize());
dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
exif_from_jpg.data = decoder.getEXIFPtr();
exif_from_jpg.data_sz = decoder.getEXIFSize();
pExif = &exif_from_jpg;
}
uhdr_compressed_image_t* final_primary_jpg_image_ptr =
new_jpg_image.data_sz == 0 ? sdr_intent_compressed : &new_jpg_image;
size_t pos = 0;
// Begin primary image
// Write SOI
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
// Write EXIF
if (pExif != nullptr) {
const size_t length = 2 + pExif->data_sz;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, pExif->data, pExif->data_sz, pos));
}
// Prepare and write XMP
if (kWriteXmpMetadata) {
const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
const size_t length = 2 + xmpNameSpaceLength + xmp_primary.size();
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos));
UHDR_ERR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
}
// Write ICC
if (pIcc != nullptr && icc_size > 0) {
const size_t length = icc_size + 2;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, pIcc, icc_size, pos));
}
// Prepare and write ISO 21496-1 metadata
if (kWriteIso21496_1Metadata) {
const size_t length = 2 + isoNameSpaceLength + 4;
uint8_t zero = 0;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos));
UHDR_ERR_CHECK(Write(dest, &zero, 1, pos));
UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes minimum_version: (00 00)
UHDR_ERR_CHECK(Write(dest, &zero, 1, pos));
UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes writer_version: (00 00)
}
// Prepare and write MPF
{
const size_t length = 2 + calculateMpfSize();
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
size_t primary_image_size = pos + length + final_primary_jpg_image_ptr->data_sz;
// between APP2 + package size + signature
// ff e2 00 58 4d 50 46 00
// 2 + 2 + 4 = 8 (bytes)
// and ff d8 sign of the secondary image
size_t secondary_image_offset = primary_image_size - pos - 8;
std::shared_ptr<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
secondary_image_size, secondary_image_offset);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
}
// Write primary image
UHDR_ERR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
final_primary_jpg_image_ptr->data_sz - 2, pos));
// Finish primary image
// Begin secondary image (gain map)
// Write SOI
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
// Prepare and write XMP
if (kWriteXmpMetadata) {
const size_t length = xmp_secondary_length;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos));
UHDR_ERR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
}
// Prepare and write ISO 21496-1 metadata
if (kWriteIso21496_1Metadata) {
const size_t length = iso_secondary_length;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos));
UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos));
UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos));
UHDR_ERR_CHECK(Write(dest, (void*)iso_secondary_data.data(), iso_secondary_data.size(), pos));
}
// Write secondary image
UHDR_ERR_CHECK(
Write(dest, (uint8_t*)gainmap_compressed->data + 2, gainmap_compressed->data_sz - 2, pos));
// Set back length
dest->data_sz = pos;
// Done!
return g_no_error;
}
uhdr_error_info_t JpegR::getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img,
jr_info_ptr uhdr_image_info) {
uhdr_compressed_image_t primary_image, gainmap;
UHDR_ERR_CHECK(extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_image, &gainmap))
UHDR_ERR_CHECK(parseJpegInfo(&primary_image, uhdr_image_info->primaryImgInfo,
&uhdr_image_info->width, &uhdr_image_info->height))
if (uhdr_image_info->gainmapImgInfo != nullptr) {
UHDR_ERR_CHECK(parseJpegInfo(&gainmap, uhdr_image_info->gainmapImgInfo))
}
return g_no_error;
}
uhdr_error_info_t JpegR::parseGainMapMetadata(uint8_t* iso_data, size_t iso_size, uint8_t* xmp_data,
size_t xmp_size,
uhdr_gainmap_metadata_ext_t* uhdr_metadata) {
if (iso_size > 0) {
if (iso_size < kIsoNameSpace.size() + 1) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_ERROR;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"iso block size needs to be atleast %zd but got %zd", kIsoNameSpace.size() + 1,
iso_size);
return status;
}
uhdr_gainmap_metadata_frac decodedMetadata;
std::vector<uint8_t> iso_vec;
for (size_t i = kIsoNameSpace.size() + 1; i < iso_size; i++) {
iso_vec.push_back(iso_data[i]);
}
UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::decodeGainmapMetadata(iso_vec, &decodedMetadata));
UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&decodedMetadata,
uhdr_metadata));
} else if (xmp_size > 0) {
UHDR_ERR_CHECK(getMetadataFromXMP(xmp_data, xmp_size, uhdr_metadata));
} else {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"received no valid buffer to parse gainmap metadata");
return status;
}
return g_no_error;
}
/* Decode API */
uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_img,
uhdr_raw_image_t* dest, float max_display_boost,
uhdr_color_transfer_t output_ct, uhdr_img_fmt_t output_format,
uhdr_raw_image_t* gainmap_img,
uhdr_gainmap_metadata_t* gainmap_metadata) {
uhdr_compressed_image_t primary_jpeg_image, gainmap_jpeg_image;
UHDR_ERR_CHECK(
extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_jpeg_image, &gainmap_jpeg_image))
JpegDecoderHelper jpeg_dec_obj_sdr;
UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(
primary_jpeg_image.data, primary_jpeg_image.data_sz,
(output_ct == UHDR_CT_SRGB) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS));
JpegDecoderHelper jpeg_dec_obj_gm;
uhdr_raw_image_t gainmap;
if (gainmap_img != nullptr || output_ct != UHDR_CT_SRGB) {
UHDR_ERR_CHECK(jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data,
gainmap_jpeg_image.data_sz, DECODE_STREAM));
gainmap = jpeg_dec_obj_gm.getDecompressedImage();
if (gainmap_img != nullptr) {
UHDR_ERR_CHECK(copy_raw_image(&gainmap, gainmap_img));
}
}
uhdr_gainmap_metadata_ext_t uhdr_metadata;
if (gainmap_metadata != nullptr || output_ct != UHDR_CT_SRGB) {
UHDR_ERR_CHECK(parseGainMapMetadata(static_cast<uint8_t*>(jpeg_dec_obj_gm.getIsoMetadataPtr()),
jpeg_dec_obj_gm.getIsoMetadataSize(),
static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata))
if (gainmap_metadata != nullptr) {
gainmap_metadata->min_content_boost = uhdr_metadata.min_content_boost;
gainmap_metadata->max_content_boost = uhdr_metadata.max_content_boost;
gainmap_metadata->gamma = uhdr_metadata.gamma;
gainmap_metadata->offset_sdr = uhdr_metadata.offset_sdr;
gainmap_metadata->offset_hdr = uhdr_metadata.offset_hdr;
gainmap_metadata->hdr_capacity_min = uhdr_metadata.hdr_capacity_min;
gainmap_metadata->hdr_capacity_max = uhdr_metadata.hdr_capacity_max;
}
}
uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage();
sdr_intent.cg =
IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize());
if (output_ct == UHDR_CT_SRGB) {
UHDR_ERR_CHECK(copy_raw_image(&sdr_intent, dest));
return g_no_error;
}
UHDR_ERR_CHECK(applyGainMap(&sdr_intent, &gainmap, &uhdr_metadata, output_ct, output_format,
max_display_boost, dest));
return g_no_error;
}
uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img,
uhdr_gainmap_metadata_ext_t* gainmap_metadata,
uhdr_color_transfer_t output_ct,
[[maybe_unused]] uhdr_img_fmt_t output_format,
float max_display_boost, uhdr_raw_image_t* dest) {
if (gainmap_metadata->version.compare(kJpegrVersion)) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"Unsupported gainmap metadata, version. Expected %s, Got %s", kJpegrVersion,
gainmap_metadata->version.c_str());
return status;
}
UHDR_ERR_CHECK(uhdr_validate_gainmap_metadata_descriptor(gainmap_metadata));
if (sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444 &&
sdr_intent->fmt != UHDR_IMG_FMT_16bppYCbCr422 &&
sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"apply gainmap method expects base image color format to be one of "
"{UHDR_IMG_FMT_24bppYCbCr444, UHDR_IMG_FMT_16bppYCbCr422, "
"UHDR_IMG_FMT_12bppYCbCr420}. Received %d",
sdr_intent->fmt);
return status;
}
if (gainmap_img->fmt != UHDR_IMG_FMT_8bppYCbCr400 &&
gainmap_img->fmt != UHDR_IMG_FMT_24bppRGB888 &&
gainmap_img->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"apply gainmap method expects gainmap image color format to be one of "
"{UHDR_IMG_FMT_8bppYCbCr400, UHDR_IMG_FMT_24bppRGB888, UHDR_IMG_FMT_32bppRGBA8888}. "
"Received %d",
gainmap_img->fmt);
return status;
}
#ifdef UHDR_ENABLE_GLES
if (mUhdrGLESCtxt != nullptr) {
if (((sdr_intent->fmt == UHDR_IMG_FMT_12bppYCbCr420 && sdr_intent->w % 2 == 0 &&
sdr_intent->h % 2 == 0) ||
(sdr_intent->fmt == UHDR_IMG_FMT_16bppYCbCr422 && sdr_intent->w % 2 == 0) ||
(sdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCr444)) &&
isBufferDataContiguous(sdr_intent) && isBufferDataContiguous(gainmap_img) &&
isBufferDataContiguous(dest)) {
// TODO: both inputs and outputs of GLES implementation assumes that raw image is contiguous
// and without strides. If not, handle the same by using temp copy
float display_boost = (std::min)(max_display_boost, gainmap_metadata->hdr_capacity_max);
return applyGainMapGLES(sdr_intent, gainmap_img, gainmap_metadata, output_ct, display_boost,
dest, static_cast<uhdr_opengl_ctxt_t*>(mUhdrGLESCtxt));
}
}
#endif
std::unique_ptr<uhdr_raw_image_ext_t> resized_gainmap = nullptr;
{
float primary_aspect_ratio = (float)sdr_intent->w / sdr_intent->h;
float gainmap_aspect_ratio = (float)gainmap_img->w / gainmap_img->h;
float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio);
// Allow 1% delta
const float delta_tolerance = 0.01;
if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) {
resized_gainmap = resize_image(gainmap_img, sdr_intent->w, sdr_intent->h);
if (resized_gainmap == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"encountered error while resizing the gainmap image from %ux%u to %ux%u",
gainmap_img->w, gainmap_img->h, sdr_intent->w, sdr_intent->h);
return status;
}
gainmap_img = resized_gainmap.get();
}
}
float map_scale_factor = (float)sdr_intent->w / gainmap_img->w;
int map_scale_factor_rnd = (std::max)(1, (int)std::roundf(map_scale_factor));
dest->cg = sdr_intent->cg;
// Table will only be used when map scale factor is integer.
ShepardsIDW idwTable(map_scale_factor_rnd);
float display_boost = (std::min)(max_display_boost, gainmap_metadata->hdr_capacity_max);
float gainmap_weight;
if (display_boost != gainmap_metadata->hdr_capacity_max) {
gainmap_weight =
(log2(display_boost) - log2(gainmap_metadata->hdr_capacity_min)) /
(log2(gainmap_metadata->hdr_capacity_max) - log2(gainmap_metadata->hdr_capacity_min));
// avoid extrapolating the gain map to fill the displayable range
gainmap_weight = CLIP3(0.0f, gainmap_weight, 1.0f);
} else {
gainmap_weight = 1.0f;
}
GainLUT gainLUT(gainmap_metadata, gainmap_weight);
GetPixelFn get_pixel_fn = getPixelFn(sdr_intent->fmt);
if (get_pixel_fn == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for reading pixels for color format %d", sdr_intent->fmt);
return status;
}
JobQueue jobQueue;
std::function<void()> applyRecMap = [sdr_intent, gainmap_img, dest, &jobQueue, &idwTable,
output_ct, &gainLUT, gainmap_metadata,
#if !USE_APPLY_GAIN_LUT
gainmap_weight,
#endif
map_scale_factor, get_pixel_fn]() -> void {
unsigned int width = sdr_intent->w;
unsigned int rowStart, rowEnd;
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; ++y) {
for (size_t x = 0; x < width; ++x) {
Color yuv_gamma_sdr = get_pixel_fn(sdr_intent, x, y);
// Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
// We are assuming the SDR base image is always sRGB transfer.
#if USE_SRGB_INVOETF_LUT
Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
#else
Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
#endif
Color rgb_hdr;
if (gainmap_img->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
float gain;
if (map_scale_factor != floorf(map_scale_factor)) {
gain = sampleMap(gainmap_img, map_scale_factor, x, y);
} else {
gain = sampleMap(gainmap_img, map_scale_factor, x, y, idwTable);
}
#if USE_APPLY_GAIN_LUT
rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT, gainmap_metadata);
#else
rgb_hdr = applyGain(rgb_sdr, gain, gainmap_metadata, gainmap_weight);
#endif
} else {
Color gain;
if (map_scale_factor != floorf(map_scale_factor)) {
gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y,
gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888);
} else {
gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y, idwTable,
gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888);
}
#if USE_APPLY_GAIN_LUT
rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT, gainmap_metadata);
#else
rgb_hdr = applyGain(rgb_sdr, gain, gainmap_metadata, gainmap_weight);
#endif
}
size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_PACKED];
switch (output_ct) {
case UHDR_CT_LINEAR: {
uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
reinterpret_cast<uint64_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = rgba_f16;
break;
}
case UHDR_CT_HLG: {
#if USE_HLG_OETF_LUT
ColorTransformFn hdrOetf = hlgOetfLUT;
#else
ColorTransformFn hdrOetf = hlgOetf;
#endif
rgb_hdr = rgb_hdr * kSdrWhiteNits / kHlgMaxNits;
rgb_hdr = hlgInverseOotfApprox(rgb_hdr);
Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
reinterpret_cast<uint32_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] =
rgba_1010102;
break;
}
case UHDR_CT_PQ: {
#if USE_PQ_OETF_LUT
ColorTransformFn hdrOetf = pqOetfLUT;
#else
ColorTransformFn hdrOetf = pqOetf;
#endif
rgb_hdr = rgb_hdr * kSdrWhiteNits / kPqMaxNits;
Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
reinterpret_cast<uint32_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] =
rgba_1010102;
break;
}
default: {
}
// Should be impossible to hit after input validation.
}
}
}
}
};
const int threads = (std::min)(GetCPUCoreCount(), 4u);
std::vector<std::thread> workers;
for (int th = 0; th < threads - 1; th++) {
workers.push_back(std::thread(applyRecMap));
}
const unsigned int rowStep = threads == 1 ? sdr_intent->h : map_scale_factor_rnd;
for (unsigned int rowStart = 0; rowStart < sdr_intent->h;) {
unsigned int rowEnd = (std::min)(rowStart + rowStep, sdr_intent->h);
jobQueue.enqueueJob(rowStart, rowEnd);
rowStart = rowEnd;
}
jobQueue.markQueueForEnd();
applyRecMap();
std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
return g_no_error;
}
uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image,
uhdr_compressed_image_t* primary_image,
uhdr_compressed_image_t* gainmap_image) {
MessageHandler msg_handler;
msg_handler.SetMessageWriter(make_unique<AlogMessageWriter>(AlogMessageWriter()));
std::shared_ptr<DataSegment> seg = DataSegment::Create(
DataRange(0, jpegr_image->data_sz), static_cast<const uint8_t*>(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()) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_ERROR;
status.has_detail = 1;
auto messages = msg_handler.GetMessages();
std::string append{};
for (auto message : messages) append += message.GetText();
snprintf(status.detail, sizeof status.detail, "%s", append.c_str());
return status;
}
const auto& jpeg_info = jpeg_info_builder.GetInfo();
const auto& image_ranges = jpeg_info.GetImageRanges();
if (image_ranges.empty()) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail, "input uhdr image does not any valid images");
return status;
}
if (primary_image != nullptr) {
primary_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[0].GetBegin();
primary_image->data_sz = image_ranges[0].GetLength();
}
if (image_ranges.size() == 1) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_INVALID_PARAM;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"input uhdr image does not contain gainmap image");
return status;
}
if (gainmap_image != nullptr) {
gainmap_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[1].GetBegin();
gainmap_image->data_sz = image_ranges[1].GetLength();
}
// TODO: choose primary image and gain map image carefully
if (image_ranges.size() > 2) {
ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
(int)image_ranges.size());
}
return g_no_error;
}
uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info,
unsigned int* img_width, unsigned int* img_height) {
JpegDecoderHelper jpeg_dec_obj;
UHDR_ERR_CHECK(jpeg_dec_obj.parseImage(jpeg_image->data, jpeg_image->data_sz))
unsigned int imgWidth, imgHeight, numComponents;
imgWidth = jpeg_dec_obj.getDecompressedImageWidth();
imgHeight = jpeg_dec_obj.getDecompressedImageHeight();
numComponents = jpeg_dec_obj.getNumComponentsInImage();
if (image_info != nullptr) {
image_info->width = imgWidth;
image_info->height = imgHeight;
image_info->numComponents = numComponents;
image_info->imgData.resize(jpeg_image->data_sz, 0);
memcpy(static_cast<void*>(image_info->imgData.data()), jpeg_image->data, jpeg_image->data_sz);
if (jpeg_dec_obj.getICCSize() != 0) {
image_info->iccData.resize(jpeg_dec_obj.getICCSize(), 0);
memcpy(static_cast<void*>(image_info->iccData.data()), jpeg_dec_obj.getICCPtr(),
jpeg_dec_obj.getICCSize());
}
if (jpeg_dec_obj.getEXIFSize() != 0) {
image_info->exifData.resize(jpeg_dec_obj.getEXIFSize(), 0);
memcpy(static_cast<void*>(image_info->exifData.data()), jpeg_dec_obj.getEXIFPtr(),
jpeg_dec_obj.getEXIFSize());
}
if (jpeg_dec_obj.getXMPSize() != 0) {
image_info->xmpData.resize(jpeg_dec_obj.getXMPSize(), 0);
memcpy(static_cast<void*>(image_info->xmpData.data()), jpeg_dec_obj.getXMPPtr(),
jpeg_dec_obj.getXMPSize());
}
if (jpeg_dec_obj.getIsoMetadataSize() != 0) {
image_info->isoData.resize(jpeg_dec_obj.getIsoMetadataSize(), 0);
memcpy(static_cast<void*>(image_info->isoData.data()), jpeg_dec_obj.getIsoMetadataPtr(),
jpeg_dec_obj.getIsoMetadataSize());
}
}
if (img_width != nullptr && img_height != nullptr) {
*img_width = imgWidth;
*img_height = imgHeight;
}
return g_no_error;
}
static float ReinhardMap(float y_hdr, float headroom) {
float out = 1.0 + y_hdr / (headroom * headroom);
out /= 1.0 + y_hdr;
return out * y_hdr;
}
GlobalTonemapOutputs globalTonemap(const std::array<float, 3>& rgb_in, float headroom,
bool is_normalized) {
// Scale to Headroom to get HDR values that are referenced to SDR white. The range [0.0, 1.0] is
// linearly stretched to [0.0, headroom].
std::array<float, 3> rgb_hdr;
std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(),
[&](float x) { return is_normalized ? x * headroom : x; });
// Apply a tone mapping to compress the range [0, headroom] to [0, 1] by
// keeping the shadows the same and crushing the highlights.
float max_hdr = *std::max_element(rgb_hdr.begin(), rgb_hdr.end());
float max_sdr = ReinhardMap(max_hdr, headroom);
std::array<float, 3> rgb_sdr;
std::transform(rgb_hdr.begin(), rgb_hdr.end(), rgb_sdr.begin(), [&](float x) {
if (x > 0.0f) {
return x * max_sdr / max_hdr;
}
return 0.0f;
});
GlobalTonemapOutputs tonemap_outputs;
tonemap_outputs.rgb_out = rgb_sdr;
tonemap_outputs.y_hdr = max_hdr;
tonemap_outputs.y_sdr = max_sdr;
return tonemap_outputs;
}
uint8_t ScaleTo8Bit(float value) {
constexpr float kMaxValFloat = 255.0f;
constexpr int kMaxValInt = 255;
return std::clamp(static_cast<int>(std::round(value * kMaxValFloat)), 0, kMaxValInt);
}
uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent) {
if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 &&
hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"tonemap method expects hdr intent color format to be one of "
"{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, "
"UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d",
hdr_intent->fmt);
return status;
}
if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 &&
sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"tonemap method expects sdr intent color format to be UHDR_IMG_FMT_12bppYCbCr420, if "
"hdr intent color format is UHDR_IMG_FMT_24bppYCbCrP010. Received %d",
sdr_intent->fmt);
return status;
}
if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444 &&
sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"tonemap method expects sdr intent color format to be UHDR_IMG_FMT_24bppYCbCr444, if "
"hdr intent color format is UHDR_IMG_FMT_30bppYCbCr444. Received %d",
sdr_intent->fmt);
return status;
}
if ((hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) &&
sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"tonemap method expects sdr intent color format to be UHDR_IMG_FMT_32bppRGBA8888, if "
"hdr intent color format is UHDR_IMG_FMT_32bppRGBA1010102 or "
"UHDR_IMG_FMT_64bppRGBAHalfFloat. Received %d",
sdr_intent->fmt);
return status;
}
ColorTransformFn hdrYuvToRgbFn = getYuvToRgbFn(hdr_intent->cg);
if (hdrYuvToRgbFn == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for converting yuv to rgb for color gamut %d",
hdr_intent->cg);
return status;
}
LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg);
if (hdrLuminanceFn == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for calculating luminance for color gamut %d",
hdr_intent->cg);
return status;
}
SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct);
if (hdrOotfFn == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for calculating Ootf for color transfer %d",
hdr_intent->ct);
return status;
}
ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct);
if (hdrInvOetf == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for converting transfer characteristics %d to linear",
hdr_intent->ct);
return status;
}
float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct);
if (hdr_white_nits == -1.0f) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"received invalid peak brightness %f nits for hdr reference display with color "
"transfer %d ",
hdr_white_nits, hdr_intent->ct);
return status;
}
GetPixelFn get_pixel_fn = getPixelFn(hdr_intent->fmt);
if (get_pixel_fn == nullptr) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for reading pixels for color format %d", hdr_intent->fmt);
return status;
}
PutPixelFn put_pixel_fn = putPixelFn(sdr_intent->fmt);
// for subsampled formats, we are writing to raw image buffers directly instead of using
// put_pixel_fn
if (put_pixel_fn == nullptr && sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
uhdr_error_info_t status;
status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
status.has_detail = 1;
snprintf(status.detail, sizeof status.detail,
"No implementation available for writing pixels for color format %d", sdr_intent->fmt);
return status;
}
sdr_intent->cg = UHDR_CG_DISPLAY_P3;
sdr_intent->ct = UHDR_CT_SRGB;
sdr_intent->range = UHDR_CR_FULL_RANGE;
ColorTransformFn hdrGamutConversionFn = getGamutConversionFn(sdr_intent->cg, hdr_intent->cg);
unsigned int height = hdr_intent->h;
const int threads = (std::min)(GetCPUCoreCount(), 4u);
// for 420 subsampling, process 2 rows at once
const int jobSizeInRows = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
unsigned int rowStep = threads == 1 ? height : jobSizeInRows;
JobQueue jobQueue;
std::function<void()> toneMapInternal;
toneMapInternal = [hdr_intent, sdr_intent, hdrInvOetf, hdrGamutConversionFn, hdrYuvToRgbFn,
hdr_white_nits, get_pixel_fn, put_pixel_fn, hdrLuminanceFn, hdrOotfFn,
&jobQueue]() -> void {
unsigned int rowStart, rowEnd;
const int hfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
const int vfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
const bool is_normalized = hdr_intent->ct != UHDR_CT_LINEAR;
uint8_t* luma_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_Y]);
uint8_t* cb_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_U]);
uint8_t* cr_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_V]);
size_t luma_stride = sdr_intent->stride[UHDR_PLANE_Y];
size_t cb_stride = sdr_intent->stride[UHDR_PLANE_U];
size_t cr_stride = sdr_intent->stride[UHDR_PLANE_V];
while (jobQueue.dequeueJob(rowStart, rowEnd)) {
for (size_t y = rowStart; y < rowEnd; y += vfactor) {
for (size_t x = 0; x < hdr_intent->w; x += hfactor) {
// meant for p010 input
float sdr_u_gamma = 0.0f;
float sdr_v_gamma = 0.0f;
for (int i = 0; i < vfactor; i++) {
for (int j = 0; j < hfactor; j++) {
Color hdr_rgb_gamma;
if (isHdrIntentRgb) {
hdr_rgb_gamma = get_pixel_fn(hdr_intent, x + j, y + i);
} else {
Color hdr_yuv_gamma = get_pixel_fn(hdr_intent, x + j, y + i);
hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
}
Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
GlobalTonemapOutputs tonemap_outputs = globalTonemap(
{hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits, is_normalized);
Color sdr_rgb_linear_bt2100 = {
{{tonemap_outputs.rgb_out[0], tonemap_outputs.rgb_out[1],
tonemap_outputs.rgb_out[2]}}};
Color sdr_rgb = hdrGamutConversionFn(sdr_rgb_linear_bt2100);
// Hard clip out-of-gamut values;
sdr_rgb = clampPixelFloat(sdr_rgb);
Color sdr_rgb_gamma = srgbOetf(sdr_rgb);
if (isSdrIntentRgb) {
put_pixel_fn(sdr_intent, (x + j), (y + i), sdr_rgb_gamma);
} else {
Color sdr_yuv_gamma = p3RgbToYuv(sdr_rgb_gamma);
sdr_yuv_gamma += {{{0.0f, 0.5f, 0.5f}}};
if (sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) {
put_pixel_fn(sdr_intent, (x + j), (y + i), sdr_yuv_gamma);
} else {
size_t out_y_idx = (y + i) * luma_stride + x + j;
luma_data[out_y_idx] = ScaleTo8Bit(sdr_yuv_gamma.y);
sdr_u_gamma += sdr_yuv_gamma.u;
sdr_v_gamma += sdr_yuv_gamma.v;
}
}
}
}
if (sdr_intent->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
sdr_u_gamma /= (hfactor * vfactor);
sdr_v_gamma /= (hfactor * vfactor);
cb_data[x / hfactor + (y / vfactor) * cb_stride] = ScaleTo8Bit(sdr_u_gamma);
cr_data[x / hfactor + (y / vfactor) * cr_stride] = ScaleTo8Bit(sdr_v_gamma);
}
}
}
}
};
// tone map
std::vector<std::thread> workers;
for (int th = 0; th < threads - 1; th++) {
workers.push_back(std::thread(toneMapInternal));
}
for (unsigned int rowStart = 0; rowStart < height;) {
unsigned int rowEnd = (std::min)(rowStart + rowStep, height);
jobQueue.enqueueJob(rowStart, rowEnd);
rowStart = rowEnd;
}
jobQueue.markQueueForEnd();
toneMapInternal();
std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
return g_no_error;
}
status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
jr_uncompressed_ptr yuv420_image_ptr,
ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest_ptr) {
if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
ALOGE("Received nullptr for input p010 image");
return ERROR_JPEGR_BAD_PTR;
}
if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
ALOGE("Image dimensions cannot be odd, image dimensions %ux%u", p010_image_ptr->width,
p010_image_ptr->height);
return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
}
if ((int)p010_image_ptr->width < kMinWidth || (int)p010_image_ptr->height < kMinHeight) {
ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %ux%u", kMinWidth,
kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
}
if ((int)p010_image_ptr->width > kMaxWidth || (int)p010_image_ptr->height > kMaxHeight) {
ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %ux%u", kMaxWidth,
kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT;
}
if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
return ERROR_JPEGR_INVALID_COLORGAMUT;
}
if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
ALOGE("Luma stride must not be smaller than width, stride=%u, width=%u",
p010_image_ptr->luma_stride, p010_image_ptr->width);
return ERROR_JPEGR_INVALID_STRIDE;
}
if (p010_image_ptr->chroma_data != nullptr &&
p010_image_ptr->chroma_stride < p010_image_ptr->width) {
ALOGE("Chroma stride must not be smaller than width, stride=%u, width=%u",
p010_image_ptr->chroma_stride, p010_image_ptr->width);
return ERROR_JPEGR_INVALID_STRIDE;
}
if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
ALOGE("Received nullptr for destination");
return ERROR_JPEGR_BAD_PTR;
}
if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
ALOGE("Invalid hdr transfer function %d", hdr_tf);
return ERROR_JPEGR_INVALID_TRANS_FUNC;
}
if (mMapDimensionScaleFactor <= 0 || mMapDimensionScaleFactor > 128) {
ALOGE("gainmap scale factor is ecpected to be in range (0, 128], received %d",
mMapDimensionScaleFactor);
return ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR;
}
if (mMapCompressQuality < 0 || mMapCompressQuality > 100) {
ALOGE("invalid quality factor %d, expects in range [0-100]", mMapCompressQuality);
return ERROR_JPEGR_INVALID_QUALITY_FACTOR;
}
if (!std::isfinite(mGamma) || mGamma <= 0.0f) {
ALOGE("unsupported gainmap gamma %f, expects to be > 0", mGamma);
return ERROR_JPEGR_INVALID_GAMMA;
}
if (mEncPreset != UHDR_USAGE_REALTIME && mEncPreset != UHDR_USAGE_BEST_QUALITY) {
ALOGE("invalid preset %d, expects one of {UHDR_USAGE_REALTIME, UHDR_USAGE_BEST_QUALITY}",
mEncPreset);
return ERROR_JPEGR_INVALID_ENC_PRESET;
}
if (!std::isfinite(mMinContentBoost) || !std::isfinite(mMaxContentBoost) ||
mMaxContentBoost < mMinContentBoost || mMinContentBoost <= 0.0f) {
ALOGE("Invalid min boost / max boost configuration. Configured max boost %f, min boost %f",
mMaxContentBoost, mMinContentBoost);
return ERROR_JPEGR_INVALID_DISPLAY_BOOST;
}
if ((!std::isfinite(mTargetDispPeakBrightness) ||
mTargetDispPeakBrightness < ultrahdr::kSdrWhiteNits ||
mTargetDispPeakBrightness > ultrahdr::kPqMaxNits) &&
mTargetDispPeakBrightness != -1.0f) {
ALOGE("unexpected target display peak brightness nits %f, expects to be with in range [%f %f]",
mTargetDispPeakBrightness, ultrahdr::kSdrWhiteNits, ultrahdr::kPqMaxNits);
return ERROR_JPEGR_INVALID_TARGET_DISP_PEAK_BRIGHTNESS;
}
if (yuv420_image_ptr == nullptr) {
return JPEGR_NO_ERROR;
}
if (yuv420_image_ptr->data == nullptr) {
ALOGE("Received nullptr for uncompressed 420 image");
return ERROR_JPEGR_BAD_PTR;
}
if (yuv420_image_ptr->luma_stride != 0 &&
yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
ALOGE("Luma stride must not be smaller than width, stride=%u, width=%u",
yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
return ERROR_JPEGR_INVALID_STRIDE;
}
if (yuv420_image_ptr->chroma_data != nullptr &&
yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
ALOGE("Chroma stride must not be smaller than (width / 2), stride=%u, width=%u",
yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
return ERROR_JPEGR_INVALID_STRIDE;
}
if (p010_image_ptr->width != yuv420_image_ptr->width ||
p010_image_ptr->height != yuv420_image_ptr->height) {
ALOGE("Image resolutions mismatch: P010: %ux%u, YUV420: %ux%u", p010_image_ptr->width,
p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
return ERROR_JPEGR_RESOLUTION_MISMATCH;
}
if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
return ERROR_JPEGR_INVALID_COLORGAMUT;
}
return JPEGR_NO_ERROR;
}
status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
jr_uncompressed_ptr yuv420_image_ptr,
ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest_ptr, int quality) {
if (quality < 0 || quality > 100) {
ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
return ERROR_JPEGR_INVALID_QUALITY_FACTOR;
}
return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
}
uhdr_color_transfer_t map_legacy_ct_to_ct(ultrahdr::ultrahdr_transfer_function ct) {
switch (ct) {
case ultrahdr::ULTRAHDR_TF_HLG:
return UHDR_CT_HLG;
case ultrahdr::ULTRAHDR_TF_PQ:
return UHDR_CT_PQ;
case ultrahdr::ULTRAHDR_TF_LINEAR:
return UHDR_CT_LINEAR;
case ultrahdr::ULTRAHDR_TF_SRGB:
return UHDR_CT_SRGB;
default:
return UHDR_CT_UNSPECIFIED;
}
}
uhdr_color_gamut_t map_legacy_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) {
switch (cg) {
case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100:
return UHDR_CG_BT_2100;
case ultrahdr::ULTRAHDR_COLORGAMUT_BT709:
return UHDR_CG_BT_709;
case ultrahdr::ULTRAHDR_COLORGAMUT_P3:
return UHDR_CG_DISPLAY_P3;
default:
return UHDR_CG_UNSPECIFIED;
}
}
ultrahdr::ultrahdr_color_gamut map_cg_to_legacy_cg(uhdr_color_gamut_t cg) {
switch (cg) {
case UHDR_CG_BT_2100:
return ultrahdr::ULTRAHDR_COLORGAMUT_BT2100;
case UHDR_CG_BT_709:
return ultrahdr::ULTRAHDR_COLORGAMUT_BT709;
case UHDR_CG_DISPLAY_P3:
return ultrahdr::ULTRAHDR_COLORGAMUT_P3;
default:
return ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
}
}
/* Encode API-0 */
status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
// validate input arguments
JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality));
if (exif != nullptr && exif->data == nullptr) {
ALOGE("received nullptr for exif metadata");
return ERROR_JPEGR_BAD_PTR;
}
// clean up input structure for later usage
jpegr_uncompressed_struct p010_image = *p010_image_ptr;
if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
if (!p010_image.chroma_data) {
uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
p010_image.chroma_stride = p010_image.luma_stride;
}
uhdr_raw_image_t hdr_intent;
hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
hdr_intent.range = p010_image.colorRange;
hdr_intent.w = p010_image.width;
hdr_intent.h = p010_image.height;
hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
hdr_intent.planes[UHDR_PLANE_V] = nullptr;
hdr_intent.stride[UHDR_PLANE_V] = 0;
uhdr_compressed_image_t output;
output.data = dest->data;
output.data_sz = 0;
output.capacity = dest->maxLength;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
uhdr_mem_block_t exifBlock;
if (exif) {
exifBlock.data = exif->data;
exifBlock.data_sz = exifBlock.capacity = exif->length;
}
auto result = encodeJPEGR(&hdr_intent, &output, quality, exif ? &exifBlock : nullptr);
if (result.error_code == UHDR_CODEC_OK) {
dest->colorGamut = map_cg_to_legacy_cg(output.cg);
dest->length = output.data_sz;
}
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
/* Encode API-1 */
status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
// validate input arguments
if (yuv420_image_ptr == nullptr) {
ALOGE("received nullptr for uncompressed 420 image");
return ERROR_JPEGR_BAD_PTR;
}
if (exif != nullptr && exif->data == nullptr) {
ALOGE("received nullptr for exif metadata");
return ERROR_JPEGR_BAD_PTR;
}
JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality))
// clean up input structure for later usage
jpegr_uncompressed_struct p010_image = *p010_image_ptr;
if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
if (!p010_image.chroma_data) {
uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
p010_image.chroma_stride = p010_image.luma_stride;
}
uhdr_raw_image_t hdr_intent;
hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
hdr_intent.range = p010_image.colorRange;
hdr_intent.w = p010_image.width;
hdr_intent.h = p010_image.height;
hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
hdr_intent.planes[UHDR_PLANE_V] = nullptr;
hdr_intent.stride[UHDR_PLANE_V] = 0;
jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
if (!yuv420_image.chroma_data) {
uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
yuv420_image.chroma_data = data + (size_t)yuv420_image.luma_stride * yuv420_image.height;
yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
}
uhdr_raw_image_t sdrRawImg;
sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420;
sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut);
sdrRawImg.ct = UHDR_CT_SRGB;
sdrRawImg.range = yuv420_image.colorRange;
sdrRawImg.w = yuv420_image.width;
sdrRawImg.h = yuv420_image.height;
sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data;
sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride;
sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data;
sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride;
uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
data += (yuv420_image.height * yuv420_image.chroma_stride) / 2;
sdrRawImg.planes[UHDR_PLANE_V] = data;
sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride;
auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg);
uhdr_compressed_image_t output;
output.data = dest->data;
output.data_sz = 0;
output.capacity = dest->maxLength;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
uhdr_mem_block_t exifBlock;
if (exif) {
exifBlock.data = exif->data;
exifBlock.data_sz = exifBlock.capacity = exif->length;
}
auto result =
encodeJPEGR(&hdr_intent, sdr_intent.get(), &output, quality, exif ? &exifBlock : nullptr);
if (result.error_code == UHDR_CODEC_OK) {
dest->colorGamut = map_cg_to_legacy_cg(output.cg);
dest->length = output.data_sz;
}
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
/* Encode API-2 */
status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
jr_uncompressed_ptr yuv420_image_ptr,
jr_compressed_ptr yuv420jpg_image_ptr,
ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
// validate input arguments
if (yuv420_image_ptr == nullptr) {
ALOGE("received nullptr for uncompressed 420 image");
return ERROR_JPEGR_BAD_PTR;
}
if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_BAD_PTR;
}
JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest))
// clean up input structure for later usage
jpegr_uncompressed_struct p010_image = *p010_image_ptr;
if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
if (!p010_image.chroma_data) {
uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
p010_image.chroma_stride = p010_image.luma_stride;
}
uhdr_raw_image_t hdr_intent;
hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
hdr_intent.range = p010_image.colorRange;
hdr_intent.w = p010_image.width;
hdr_intent.h = p010_image.height;
hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
hdr_intent.planes[UHDR_PLANE_V] = nullptr;
hdr_intent.stride[UHDR_PLANE_V] = 0;
jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
if (!yuv420_image.chroma_data) {
uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
yuv420_image.chroma_data = data + (size_t)yuv420_image.luma_stride * p010_image.height;
yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
}
uhdr_raw_image_t sdrRawImg;
sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420;
sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut);
sdrRawImg.ct = UHDR_CT_SRGB;
sdrRawImg.range = yuv420_image.colorRange;
sdrRawImg.w = yuv420_image.width;
sdrRawImg.h = yuv420_image.height;
sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data;
sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride;
sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data;
sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride;
uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
data += (yuv420_image.height * yuv420_image.chroma_stride) / 2;
sdrRawImg.planes[UHDR_PLANE_V] = data;
sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride;
auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg);
uhdr_compressed_image_t input;
input.data = yuv420jpg_image_ptr->data;
input.data_sz = yuv420jpg_image_ptr->length;
input.capacity = yuv420jpg_image_ptr->maxLength;
input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut);
input.ct = UHDR_CT_UNSPECIFIED;
input.range = UHDR_CR_UNSPECIFIED;
uhdr_compressed_image_t output;
output.data = dest->data;
output.data_sz = 0;
output.capacity = dest->maxLength;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
auto result = encodeJPEGR(&hdr_intent, sdr_intent.get(), &input, &output);
if (result.error_code == UHDR_CODEC_OK) {
dest->colorGamut = map_cg_to_legacy_cg(output.cg);
dest->length = output.data_sz;
}
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
/* Encode API-3 */
status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
jr_compressed_ptr yuv420jpg_image_ptr,
ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
// validate input arguments
if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_BAD_PTR;
}
JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest))
// clean up input structure for later usage
jpegr_uncompressed_struct p010_image = *p010_image_ptr;
if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
if (!p010_image.chroma_data) {
uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height;
p010_image.chroma_stride = p010_image.luma_stride;
}
uhdr_raw_image_t hdr_intent;
hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut);
hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf);
hdr_intent.range = p010_image.colorRange;
hdr_intent.w = p010_image.width;
hdr_intent.h = p010_image.height;
hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data;
hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride;
hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data;
hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride;
hdr_intent.planes[UHDR_PLANE_V] = nullptr;
hdr_intent.stride[UHDR_PLANE_V] = 0;
uhdr_compressed_image_t input;
input.data = yuv420jpg_image_ptr->data;
input.data_sz = yuv420jpg_image_ptr->length;
input.capacity = yuv420jpg_image_ptr->maxLength;
input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut);
input.ct = UHDR_CT_UNSPECIFIED;
input.range = UHDR_CR_UNSPECIFIED;
uhdr_compressed_image_t output;
output.data = dest->data;
output.data_sz = 0;
output.capacity = dest->maxLength;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
auto result = encodeJPEGR(&hdr_intent, &input, &output);
if (result.error_code == UHDR_CODEC_OK) {
dest->colorGamut = map_cg_to_legacy_cg(output.cg);
dest->length = output.data_sz;
}
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
/* Encode API-4 */
status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest) {
if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpeg image");
return ERROR_JPEGR_BAD_PTR;
}
if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed gain map");
return ERROR_JPEGR_BAD_PTR;
}
if (dest == nullptr || dest->data == nullptr) {
ALOGE("received nullptr for destination");
return ERROR_JPEGR_BAD_PTR;
}
uhdr_compressed_image_t input;
input.data = yuv420jpg_image_ptr->data;
input.data_sz = yuv420jpg_image_ptr->length;
input.capacity = yuv420jpg_image_ptr->maxLength;
input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut);
input.ct = UHDR_CT_UNSPECIFIED;
input.range = UHDR_CR_UNSPECIFIED;
uhdr_compressed_image_t gainmap;
gainmap.data = gainmapjpg_image_ptr->data;
gainmap.data_sz = gainmapjpg_image_ptr->length;
gainmap.capacity = gainmapjpg_image_ptr->maxLength;
gainmap.cg = UHDR_CG_UNSPECIFIED;
gainmap.ct = UHDR_CT_UNSPECIFIED;
gainmap.range = UHDR_CR_UNSPECIFIED;
uhdr_compressed_image_t output;
output.data = dest->data;
output.data_sz = 0;
output.capacity = dest->maxLength;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
uhdr_gainmap_metadata_ext_t meta;
meta.version = metadata->version;
meta.hdr_capacity_max = metadata->hdrCapacityMax;
meta.hdr_capacity_min = metadata->hdrCapacityMin;
meta.gamma = metadata->gamma;
meta.offset_sdr = metadata->offsetSdr;
meta.offset_hdr = metadata->offsetHdr;
meta.max_content_boost = metadata->maxContentBoost;
meta.min_content_boost = metadata->minContentBoost;
auto result = encodeJPEGR(&input, &gainmap, &meta, &output);
if (result.error_code == UHDR_CODEC_OK) {
dest->colorGamut = map_cg_to_legacy_cg(output.cg);
dest->length = output.data_sz;
}
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
/* Decode API */
status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr) {
if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpegr image");
return ERROR_JPEGR_BAD_PTR;
}
if (jpegr_image_info_ptr == nullptr) {
ALOGE("received nullptr for compressed jpegr info struct");
return ERROR_JPEGR_BAD_PTR;
}
uhdr_compressed_image_t input;
input.data = jpegr_image_ptr->data;
input.data_sz = jpegr_image_ptr->length;
input.capacity = jpegr_image_ptr->maxLength;
input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut);
input.ct = UHDR_CT_UNSPECIFIED;
input.range = UHDR_CR_UNSPECIFIED;
auto result = getJPEGRInfo(&input, jpegr_image_info_ptr);
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
float max_display_boost, jr_exif_ptr exif,
ultrahdr_output_format output_format,
jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
ALOGE("received nullptr for compressed jpegr image");
return ERROR_JPEGR_BAD_PTR;
}
if (dest == nullptr || dest->data == nullptr) {
ALOGE("received nullptr for dest image");
return ERROR_JPEGR_BAD_PTR;
}
if (max_display_boost < 1.0f) {
ALOGE("received bad value for max_display_boost %f", max_display_boost);
return ERROR_JPEGR_INVALID_DISPLAY_BOOST;
}
if (exif != nullptr && exif->data == nullptr) {
ALOGE("received nullptr address for exif data");
return ERROR_JPEGR_BAD_PTR;
}
if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) {
ALOGE("received nullptr address for gainmap data");
return ERROR_JPEGR_BAD_PTR;
}
if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
ALOGE("received bad value for output format %d", output_format);
return ERROR_JPEGR_INVALID_OUTPUT_FORMAT;
}
uhdr_color_transfer_t ct;
uhdr_img_fmt fmt;
if (output_format == ULTRAHDR_OUTPUT_HDR_HLG) {
fmt = UHDR_IMG_FMT_32bppRGBA1010102;
ct = UHDR_CT_HLG;
} else if (output_format == ULTRAHDR_OUTPUT_HDR_PQ) {
fmt = UHDR_IMG_FMT_32bppRGBA1010102;
ct = UHDR_CT_PQ;
} else if (output_format == ULTRAHDR_OUTPUT_HDR_LINEAR) {
fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat;
ct = UHDR_CT_LINEAR;
} else if (output_format == ULTRAHDR_OUTPUT_SDR) {
fmt = UHDR_IMG_FMT_32bppRGBA8888;
ct = UHDR_CT_SRGB;
}
uhdr_compressed_image_t input;
input.data = jpegr_image_ptr->data;
input.data_sz = jpegr_image_ptr->length;
input.capacity = jpegr_image_ptr->maxLength;
input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut);
input.ct = UHDR_CT_UNSPECIFIED;
input.range = UHDR_CR_UNSPECIFIED;
jpeg_info_struct primary_image;
jpeg_info_struct gainmap_image;
jpegr_info_struct jpegr_info;
jpegr_info.primaryImgInfo = &primary_image;
jpegr_info.gainmapImgInfo = &gainmap_image;
if (getJPEGRInfo(&input, &jpegr_info).error_code != UHDR_CODEC_OK) return JPEGR_UNKNOWN_ERROR;
if (exif != nullptr) {
if (exif->length < primary_image.exifData.size()) {
return ERROR_JPEGR_BUFFER_TOO_SMALL;
}
memcpy(exif->data, primary_image.exifData.data(), primary_image.exifData.size());
exif->length = primary_image.exifData.size();
}
uhdr_raw_image_t output;
output.fmt = fmt;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
output.w = jpegr_info.width;
output.h = jpegr_info.height;
output.planes[UHDR_PLANE_PACKED] = dest->data;
output.stride[UHDR_PLANE_PACKED] = jpegr_info.width;
output.planes[UHDR_PLANE_U] = nullptr;
output.stride[UHDR_PLANE_U] = 0;
output.planes[UHDR_PLANE_V] = nullptr;
output.stride[UHDR_PLANE_V] = 0;
uhdr_raw_image_t output_gm;
if (gainmap_image_ptr) {
output.fmt =
gainmap_image.numComponents == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_24bppRGB888;
output.cg = UHDR_CG_UNSPECIFIED;
output.ct = UHDR_CT_UNSPECIFIED;
output.range = UHDR_CR_UNSPECIFIED;
output.w = gainmap_image.width;
output.h = gainmap_image.height;
output.planes[UHDR_PLANE_PACKED] = gainmap_image_ptr->data;
output.stride[UHDR_PLANE_PACKED] = gainmap_image.width;
output.planes[UHDR_PLANE_U] = nullptr;
output.stride[UHDR_PLANE_U] = 0;
output.planes[UHDR_PLANE_V] = nullptr;
output.stride[UHDR_PLANE_V] = 0;
}
uhdr_gainmap_metadata_ext_t meta;
auto result = decodeJPEGR(&input, &output, max_display_boost, ct, fmt,
gainmap_image_ptr ? &output_gm : nullptr, metadata ? &meta : nullptr);
if (result.error_code == UHDR_CODEC_OK) {
dest->width = output.w;
dest->height = output.h;
dest->colorGamut = map_cg_to_legacy_cg(output.cg);
dest->colorRange = output.range;
dest->pixelFormat = output.fmt;
dest->chroma_data = nullptr;
if (gainmap_image_ptr) {
gainmap_image_ptr->width = output_gm.w;
gainmap_image_ptr->height = output_gm.h;
gainmap_image_ptr->colorGamut = map_cg_to_legacy_cg(output_gm.cg);
gainmap_image_ptr->colorRange = output_gm.range;
gainmap_image_ptr->pixelFormat = output_gm.fmt;
gainmap_image_ptr->chroma_data = nullptr;
}
if (metadata) {
metadata->version = meta.version;
metadata->hdrCapacityMax = meta.hdr_capacity_max;
metadata->hdrCapacityMin = meta.hdr_capacity_min;
metadata->gamma = meta.gamma;
metadata->offsetSdr = meta.offset_sdr;
metadata->offsetHdr = meta.offset_hdr;
metadata->maxContentBoost = meta.max_content_boost;
metadata->minContentBoost = meta.min_content_boost;
}
}
return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR;
}
} // namespace ultrahdr