/* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "ultrahdr_api.h" #include "ultrahdr/ultrahdrcommon.h" #include "ultrahdr/gainmapmath.h" #include "ultrahdr/editorhelper.h" #include "ultrahdr/jpegr.h" #include "ultrahdr/jpegrutils.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 photos_editing_formats::image_io; namespace ultrahdr { uhdr_memory_block::uhdr_memory_block(size_t capacity) { m_buffer = std::make_unique(capacity); m_capacity = capacity; } uhdr_raw_image_ext::uhdr_raw_image_ext(uhdr_img_fmt_t fmt_, uhdr_color_gamut_t cg_, uhdr_color_transfer_t ct_, uhdr_color_range_t range_, unsigned w_, unsigned h_, unsigned align_stride_to) { this->fmt = fmt_; this->cg = cg_; this->ct = ct_; this->range = range_; this->w = w_; this->h = h_; int aligned_width = ALIGNM(w_, align_stride_to); size_t bpp = 1; if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010 || fmt_ == UHDR_IMG_FMT_30bppYCbCr444) { bpp = 2; } else if (fmt_ == UHDR_IMG_FMT_24bppRGB888) { bpp = 3; } else if (fmt_ == UHDR_IMG_FMT_32bppRGBA8888 || fmt_ == UHDR_IMG_FMT_32bppRGBA1010102) { bpp = 4; } else if (fmt_ == UHDR_IMG_FMT_64bppRGBAHalfFloat) { bpp = 8; } size_t plane_1_sz = bpp * aligned_width * h_; size_t plane_2_sz; size_t plane_3_sz; if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010) { plane_2_sz = (2 /* planes */ * bpp * (aligned_width / 2) * (h_ / 2)); plane_3_sz = 0; } else if (fmt_ == UHDR_IMG_FMT_30bppYCbCr444 || fmt_ == UHDR_IMG_FMT_24bppYCbCr444) { plane_2_sz = bpp * aligned_width * h_; plane_3_sz = bpp * aligned_width * h_; } else if (fmt_ == UHDR_IMG_FMT_12bppYCbCr420) { plane_2_sz = (bpp * (aligned_width / 2) * (h_ / 2)); plane_3_sz = (bpp * (aligned_width / 2) * (h_ / 2)); } else { plane_2_sz = 0; plane_3_sz = 0; } size_t total_size = plane_1_sz + plane_2_sz + plane_3_sz; this->m_block = std::make_unique(total_size); uint8_t* data = this->m_block->m_buffer.get(); this->planes[UHDR_PLANE_Y] = data; this->stride[UHDR_PLANE_Y] = aligned_width; if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010) { this->planes[UHDR_PLANE_UV] = data + plane_1_sz; this->stride[UHDR_PLANE_UV] = aligned_width; this->planes[UHDR_PLANE_V] = nullptr; this->stride[UHDR_PLANE_V] = 0; } else if (fmt_ == UHDR_IMG_FMT_30bppYCbCr444 || fmt_ == UHDR_IMG_FMT_24bppYCbCr444) { this->planes[UHDR_PLANE_U] = data + plane_1_sz; this->stride[UHDR_PLANE_U] = aligned_width; this->planes[UHDR_PLANE_V] = data + plane_1_sz + plane_2_sz; this->stride[UHDR_PLANE_V] = aligned_width; } else if (fmt_ == UHDR_IMG_FMT_12bppYCbCr420) { this->planes[UHDR_PLANE_U] = data + plane_1_sz; this->stride[UHDR_PLANE_U] = aligned_width / 2; this->planes[UHDR_PLANE_V] = data + plane_1_sz + plane_2_sz; this->stride[UHDR_PLANE_V] = aligned_width / 2; } else { this->planes[UHDR_PLANE_U] = nullptr; this->stride[UHDR_PLANE_U] = 0; this->planes[UHDR_PLANE_V] = nullptr; this->stride[UHDR_PLANE_V] = 0; } } uhdr_compressed_image_ext::uhdr_compressed_image_ext(uhdr_color_gamut_t cg_, uhdr_color_transfer_t ct_, uhdr_color_range_t range_, size_t size) { this->m_block = std::make_unique(size); this->data = this->m_block->m_buffer.get(); this->capacity = size; this->data_sz = 0; this->cg = cg_; this->ct = ct_; this->range = range_; } uhdr_error_info_t apply_effects(uhdr_encoder_private* enc) { for (auto& it : enc->m_effects) { std::unique_ptr hdr_img = nullptr; std::unique_ptr sdr_img = nullptr; if (nullptr != dynamic_cast(it)) { auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second; hdr_img = apply_rotate(dynamic_cast(it), hdr_raw_entry.get()); if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) { auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second; sdr_img = apply_rotate(dynamic_cast(it), sdr_raw_entry.get()); } } else if (nullptr != dynamic_cast(it)) { auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second; hdr_img = apply_mirror(dynamic_cast(it), hdr_raw_entry.get()); if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) { auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second; sdr_img = apply_mirror(dynamic_cast(it), sdr_raw_entry.get()); } } else if (nullptr != dynamic_cast(it)) { auto crop_effect = dynamic_cast(it); auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second; int left = (std::max)(0, crop_effect->m_left); int right = (std::min)((int)hdr_raw_entry->w, crop_effect->m_right); int crop_width = right - left; if (crop_width <= 0) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop width is expected to be > 0, crop width is %d", crop_width); return status; } if (crop_width % 2 != 0 && hdr_raw_entry->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop width is expected to even for format " "{UHDR_IMG_FMT_24bppYCbCrP010}, crop width is %d", crop_width); return status; } int top = (std::max)(0, crop_effect->m_top); int bottom = (std::min)((int)hdr_raw_entry->h, crop_effect->m_bottom); int crop_height = bottom - top; if (crop_height <= 0) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop height is expected to be > 0, crop height is %d", crop_height); return status; } if (crop_height % 2 != 0 && hdr_raw_entry->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop height is expected to even for format " "{UHDR_IMG_FMT_24bppYCbCrP010}. crop height is %d", crop_height); return status; } hdr_img = apply_crop(dynamic_cast(it), hdr_raw_entry.get(), left, top, crop_width, crop_height); if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) { auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second; if (crop_width % 2 != 0 && sdr_raw_entry->fmt == UHDR_IMG_FMT_12bppYCbCr420) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop width is expected to even for format " "{UHDR_IMG_FMT_12bppYCbCr420}, crop width is %d", crop_width); return status; } if (crop_height % 2 != 0 && sdr_raw_entry->fmt == UHDR_IMG_FMT_12bppYCbCr420) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop height is expected to even for format " "{UHDR_IMG_FMT_12bppYCbCr420}. crop height is %d", crop_height); return status; } sdr_img = apply_crop(dynamic_cast(it), sdr_raw_entry.get(), left, top, crop_width, crop_height); } } else if (nullptr != dynamic_cast(it)) { auto resize_effect = dynamic_cast(it); int dst_w = resize_effect->m_width; int dst_h = resize_effect->m_height; auto& hdr_raw_entry = enc->m_raw_images.find(UHDR_HDR_IMG)->second; if (dst_w <= 0 || dst_h <= 0 || dst_w > ultrahdr::kMaxWidth || dst_h > ultrahdr::kMaxHeight) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; snprintf(status.detail, sizeof status.detail, "destination dimensions must be in range (0, %d] x (0, %d]. dest image width " "is %d, dest image height is %d", ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, dst_w, dst_h); return status; } if ((dst_w % 2 != 0 || dst_h % 2 != 0) && hdr_raw_entry->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; snprintf(status.detail, sizeof status.detail, "destination dimensions cannot be odd for format {UHDR_IMG_FMT_24bppYCbCrP010}. " "dest image width is %d, dest image height is %d", dst_w, dst_h); return status; } hdr_img = apply_resize(dynamic_cast(it), hdr_raw_entry.get(), dst_w, dst_h); if (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end()) { auto& sdr_raw_entry = enc->m_raw_images.find(UHDR_SDR_IMG)->second; if ((dst_w % 2 != 0 || dst_h % 2 != 0) && sdr_raw_entry->fmt == UHDR_IMG_FMT_12bppYCbCr420) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; snprintf(status.detail, sizeof status.detail, "destination dimensions cannot be odd for format {UHDR_IMG_FMT_12bppYCbCr420}. " "dest image width is %d, dest image height is %d", dst_w, dst_h); return status; } sdr_img = apply_resize(dynamic_cast(it), sdr_raw_entry.get(), dst_w, dst_h); } } if (hdr_img == nullptr || (enc->m_raw_images.find(UHDR_SDR_IMG) != enc->m_raw_images.end() && sdr_img == nullptr)) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_UNKNOWN_ERROR; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "encountered unknown error while applying effect %s", it->to_string().c_str()); return status; } enc->m_raw_images.insert_or_assign(UHDR_HDR_IMG, std::move(hdr_img)); if (sdr_img != nullptr) { enc->m_raw_images.insert_or_assign(UHDR_SDR_IMG, std::move(sdr_img)); } } return g_no_error; } bool is_resize_effect(const ultrahdr::uhdr_effect_desc_t* effect) { return dynamic_cast(effect) != nullptr; } uhdr_error_info_t apply_effects(uhdr_decoder_private* dec) { void *gl_ctxt = nullptr, *disp_texture_ptr = nullptr, *gm_texture_ptr = nullptr; #ifdef UHDR_ENABLE_GLES if (dec->m_enable_gles) { gl_ctxt = &dec->m_uhdr_gl_ctxt; bool texture_created = dec->m_uhdr_gl_ctxt.mDecodedImgTexture != 0 && dec->m_uhdr_gl_ctxt.mGainmapImgTexture != 0; bool resize_effect_present = std::find_if(dec->m_effects.begin(), dec->m_effects.end(), is_resize_effect) != dec->m_effects.end(); if (!texture_created && resize_effect_present && isBufferDataContiguous(dec->m_decoded_img_buffer.get()) && isBufferDataContiguous(dec->m_gainmap_img_buffer.get())) { dec->m_uhdr_gl_ctxt.mDecodedImgTexture = dec->m_uhdr_gl_ctxt.create_texture( dec->m_decoded_img_buffer->fmt, dec->m_decoded_img_buffer->w, dec->m_decoded_img_buffer->h, dec->m_decoded_img_buffer->planes[0]); dec->m_uhdr_gl_ctxt.mGainmapImgTexture = dec->m_uhdr_gl_ctxt.create_texture( dec->m_gainmap_img_buffer->fmt, dec->m_gainmap_img_buffer->w, dec->m_gainmap_img_buffer->h, dec->m_gainmap_img_buffer->planes[0]); } disp_texture_ptr = &dec->m_uhdr_gl_ctxt.mDecodedImgTexture; gm_texture_ptr = &dec->m_uhdr_gl_ctxt.mGainmapImgTexture; } #endif for (auto& it : dec->m_effects) { std::unique_ptr disp_img = nullptr; std::unique_ptr gm_img = nullptr; if (nullptr != dynamic_cast(it)) { disp_img = apply_rotate(dynamic_cast(it), dec->m_decoded_img_buffer.get(), gl_ctxt, disp_texture_ptr); gm_img = apply_rotate(dynamic_cast(it), dec->m_gainmap_img_buffer.get(), gl_ctxt, gm_texture_ptr); } else if (nullptr != dynamic_cast(it)) { disp_img = apply_mirror(dynamic_cast(it), dec->m_decoded_img_buffer.get(), gl_ctxt, disp_texture_ptr); gm_img = apply_mirror(dynamic_cast(it), dec->m_gainmap_img_buffer.get(), gl_ctxt, gm_texture_ptr); } else if (nullptr != dynamic_cast(it)) { auto crop_effect = dynamic_cast(it); uhdr_raw_image_t* disp = dec->m_decoded_img_buffer.get(); uhdr_raw_image_t* gm = dec->m_gainmap_img_buffer.get(); int left = (std::max)(0, crop_effect->m_left); int right = (std::min)((int)disp->w, crop_effect->m_right); if (right <= left) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "unexpected crop dimensions. crop right is <= crop left, after crop image width is %d", right - left); return status; } int top = (std::max)(0, crop_effect->m_top); int bottom = (std::min)((int)disp->h, crop_effect->m_bottom); if (bottom <= top) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "unexpected crop dimensions. crop bottom is <= crop top, after crop image height is %d", bottom - top); return status; } float wd_ratio = ((float)disp->w) / gm->w; float ht_ratio = ((float)disp->h) / gm->h; int gm_left = (int)(left / wd_ratio); int gm_right = (int)(right / wd_ratio); if (gm_right <= gm_left) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop right is <= crop left for gainmap image, after " "crop gainmap image width is %d", gm_right - gm_left); return status; } int gm_top = (int)(top / ht_ratio); int gm_bottom = (int)(bottom / ht_ratio); if (gm_bottom <= gm_top) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unexpected crop dimensions. crop bottom is <= crop top for gainmap image, after " "crop gainmap image height is %d", gm_bottom - gm_top); return status; } disp_img = apply_crop(dynamic_cast(it), disp, left, top, right - left, bottom - top, gl_ctxt, disp_texture_ptr); gm_img = apply_crop(dynamic_cast(it), gm, gm_left, gm_top, (gm_right - gm_left), (gm_bottom - gm_top), gl_ctxt, gm_texture_ptr); } else if (nullptr != dynamic_cast(it)) { auto resize_effect = dynamic_cast(it); int dst_w = resize_effect->m_width; int dst_h = resize_effect->m_height; float wd_ratio = ((float)dec->m_decoded_img_buffer.get()->w) / dec->m_gainmap_img_buffer.get()->w; float ht_ratio = ((float)dec->m_decoded_img_buffer.get()->h) / dec->m_gainmap_img_buffer.get()->h; int dst_gm_w = (int)(dst_w / wd_ratio); int dst_gm_h = (int)(dst_h / ht_ratio); if (dst_w <= 0 || dst_h <= 0 || dst_gm_w <= 0 || dst_gm_h <= 0 || dst_w > ultrahdr::kMaxWidth || dst_h > ultrahdr::kMaxHeight || dst_gm_w > ultrahdr::kMaxWidth || dst_gm_h > ultrahdr::kMaxHeight) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; snprintf(status.detail, sizeof status.detail, "destination dimension must be in range (0, %d] x (0, %d]. dest image width is " "%d, dest image height is %d, dest gainmap width is %d, dest gainmap height is %d", ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, dst_w, dst_h, dst_gm_w, dst_gm_h); return status; } disp_img = apply_resize(dynamic_cast(it), dec->m_decoded_img_buffer.get(), dst_w, dst_h, gl_ctxt, disp_texture_ptr); gm_img = apply_resize(dynamic_cast(it), dec->m_gainmap_img_buffer.get(), dst_gm_w, dst_gm_h, gl_ctxt, gm_texture_ptr); } if (disp_img == nullptr || gm_img == nullptr) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_UNKNOWN_ERROR; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "encountered unknown error while applying effect %s", it->to_string().c_str()); return status; } dec->m_decoded_img_buffer = std::move(disp_img); dec->m_gainmap_img_buffer = std::move(gm_img); } return g_no_error; } uhdr_error_info_t uhdr_validate_gainmap_metadata_descriptor(uhdr_gainmap_metadata_t* metadata) { uhdr_error_info_t status = g_no_error; if (metadata == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for gainmap metadata descriptor"); } else if (!std::isfinite(metadata->min_content_boost) || !std::isfinite(metadata->max_content_boost) || !std::isfinite(metadata->offset_sdr) || !std::isfinite(metadata->offset_hdr) || !std::isfinite(metadata->hdr_capacity_min) || !std::isfinite(metadata->hdr_capacity_max) || !std::isfinite(metadata->gamma)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "Field(s) of gainmap metadata descriptor are either NaN or infinite. min content " "boost %f, max content boost %f, offset sdr %f, offset hdr %f, hdr capacity min %f, " "hdr capacity max %f, gamma %f", metadata->min_content_boost, metadata->max_content_boost, metadata->offset_sdr, metadata->offset_hdr, metadata->hdr_capacity_min, metadata->hdr_capacity_max, metadata->gamma); } else if (metadata->max_content_boost < metadata->min_content_boost) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for content boost max %f, expects to be >= content boost min %f", metadata->max_content_boost, metadata->min_content_boost); } else if (metadata->min_content_boost <= 0.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for min boost %f, expects > 0.0f", metadata->min_content_boost); return status; } else if (metadata->gamma <= 0.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for gamma %f, expects > 0.0f", metadata->gamma); } else if (metadata->offset_sdr < 0.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for offset sdr %f, expects to be >= 0.0f", metadata->offset_sdr); } else if (metadata->offset_hdr < 0.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for offset hdr %f, expects to be >= 0.0f", metadata->offset_hdr); } else if (metadata->hdr_capacity_max <= metadata->hdr_capacity_min) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for hdr capacity max %f, expects to be > hdr capacity min %f", metadata->hdr_capacity_max, metadata->hdr_capacity_min); } else if (metadata->hdr_capacity_min < 1.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received bad value for hdr capacity min %f, expects to be >= 1.0f", metadata->hdr_capacity_min); } return status; } } // namespace ultrahdr uhdr_codec_private::~uhdr_codec_private() { for (auto it : m_effects) delete it; m_effects.clear(); } uhdr_error_info_t uhdr_enc_validate_and_set_compressed_img(uhdr_codec_private_t* enc, uhdr_compressed_image_t* img, uhdr_img_label_t intent) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (img == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle"); } else if (img->data == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for compressed img->data field"); } else if (img->capacity < img->data_sz) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "img->capacity %zd is less than img->data_sz %zd", img->capacity, img->data_sz); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } std::shared_ptr seg = DataSegment::Create(DataRange(0, img->data_sz), static_cast(img->data), DataSegment::BufferDispositionPolicy::kDontDelete); DataSegmentDataSource data_source(seg); JpegInfoBuilder jpeg_info_builder; JpegScanner jpeg_scanner(nullptr); jpeg_scanner.Run(&data_source, &jpeg_info_builder); data_source.Reset(); if (jpeg_scanner.HasError()) { status.error_code = UHDR_CODEC_INVALID_PARAM; snprintf(status.detail, sizeof status.detail, "received bad/corrupted jpeg image as part of input configuration"); return status; } const auto& image_ranges = jpeg_info_builder.GetInfo().GetImageRanges(); if (image_ranges.empty()) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "compressed image received as part of input config contains no valid jpeg images"); return status; } if (image_ranges.size() > 1) { ALOGW( "compressed image received as part of input config contains multiple jpeg images, " "selecting first image for intent %d, rest are ignored", intent); } auto entry = std::make_unique(img->cg, img->ct, img->range, image_ranges[0].GetLength()); memcpy(entry->data, static_cast(img->data) + image_ranges[0].GetBegin(), image_ranges[0].GetLength()); entry->data_sz = image_ranges[0].GetLength(); handle->m_compressed_images.insert_or_assign(intent, std::move(entry)); return status; } uhdr_codec_private_t* uhdr_create_encoder(void) { uhdr_encoder_private* handle = new uhdr_encoder_private(); if (handle != nullptr) { uhdr_reset_encoder(handle); } return handle; } void uhdr_release_encoder(uhdr_codec_private_t* enc) { if (dynamic_cast(enc) != nullptr) { uhdr_encoder_private* handle = dynamic_cast(enc); delete handle; } } UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_using_multi_channel_gainmap(uhdr_codec_private_t* enc, int use_multi_channel_gainmap) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_use_multi_channel_gainmap = use_multi_channel_gainmap; return status; } UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_gainmap_scale_factor(uhdr_codec_private_t* enc, int gainmap_scale_factor) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (gainmap_scale_factor <= 0 || gainmap_scale_factor > 128) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "gainmap scale factor is expected to be in range (0, 128], received %d", gainmap_scale_factor); return status; } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_gainmap_scale_factor = gainmap_scale_factor; return status; } UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_gainmap_gamma(uhdr_codec_private_t* enc, float gamma) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (!std::isfinite(gamma) || gamma <= 0.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unsupported gainmap gamma %f, expects to be > 0", gamma); return status; } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_gamma = gamma; return status; } uhdr_error_info_t uhdr_enc_set_preset(uhdr_codec_private_t* enc, uhdr_enc_preset_t preset) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (preset != UHDR_USAGE_REALTIME && preset != UHDR_USAGE_BEST_QUALITY) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid preset %d, expects one of {UHDR_USAGE_REALTIME, UHDR_USAGE_BEST_QUALITY}", preset); return status; } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_enc_preset = preset; return status; } uhdr_error_info_t uhdr_enc_set_min_max_content_boost(uhdr_codec_private_t* enc, float min_boost, float max_boost) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (!std::isfinite(min_boost) || !std::isfinite(max_boost)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received an argument with value either NaN or infinite. Configured min boost %f, " "max boost %f", max_boost, min_boost); return status; } if (max_boost < min_boost) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "Invalid min boost / max boost configuration. configured max boost %f is less than " "min boost %f", max_boost, min_boost); return status; } if (min_boost <= 0.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "Invalid min boost configuration %f, expects > 0.0f", min_boost); return status; } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_min_content_boost = min_boost; handle->m_max_content_boost = max_boost; return status; } uhdr_error_info_t uhdr_enc_set_target_display_peak_brightness(uhdr_codec_private_t* enc, float nits) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (!std::isfinite(nits) || nits < ultrahdr::kSdrWhiteNits || nits > ultrahdr::kPqMaxNits) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "unexpected target display peak brightness nits %f, expects to be with in range [%f, %f]", nits, ultrahdr::kSdrWhiteNits, ultrahdr::kPqMaxNits); } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_target_disp_max_brightness = nits; return status; } uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_image_t* img, uhdr_img_label_t intent) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (img == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for raw image handle"); } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG}", intent); } else if (intent == UHDR_HDR_IMG && (img->fmt != UHDR_IMG_FMT_24bppYCbCrP010 && img->fmt != UHDR_IMG_FMT_32bppRGBA1010102 && img->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unsupported input pixel format for hdr intent %d, expects one of " "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_32bppRGBA1010102, " "UHDR_IMG_FMT_64bppRGBAHalfFloat}", img->fmt); } else if (intent == UHDR_SDR_IMG && (img->fmt != UHDR_IMG_FMT_12bppYCbCr420 && img->fmt != UHDR_IMG_FMT_32bppRGBA8888)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unsupported input pixel format for sdr intent %d, expects one of " "{UHDR_IMG_FMT_12bppYCbCr420, UHDR_IMG_FMT_32bppRGBA8888}", img->fmt); } else if (img->cg != UHDR_CG_BT_2100 && img->cg != UHDR_CG_DISPLAY_P3 && img->cg != UHDR_CG_BT_709) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid input color gamut %d, expects one of {UHDR_CG_BT_2100, UHDR_CG_DISPLAY_P3, " "UHDR_CG_BT_709}", img->cg); } else if (intent == UHDR_SDR_IMG && img->ct != UHDR_CT_SRGB) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid input color transfer for sdr intent image %d, expects UHDR_CT_SRGB", img->ct); } else if (intent == UHDR_HDR_IMG && img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat && img->ct != UHDR_CT_LINEAR) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid input color transfer for hdr intent image %d with format " "UHDR_IMG_FMT_64bppRGBAHalfFloat, expects one of {UHDR_CT_LINEAR}", img->ct); } else if (intent == UHDR_HDR_IMG && img->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat && (img->ct != UHDR_CT_HLG && img->ct != UHDR_CT_PQ)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid input color transfer for hdr intent image %d with format %d, expects one of " "{UHDR_CT_HLG, UHDR_CT_PQ}", img->fmt, img->ct); } else if ((img->w % 2 != 0 || img->h % 2 != 0) && (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 || img->fmt == UHDR_IMG_FMT_24bppYCbCrP010)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image dimensions cannot be odd for formats {UHDR_IMG_FMT_12bppYCbCr420, " "UHDR_IMG_FMT_24bppYCbCrP010}, received image dimensions %dx%d", img->w, img->h); } else if ((int)img->w < ultrahdr::kMinWidth || (int)img->h < ultrahdr::kMinHeight) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image dimensions cannot be less than %dx%d, received image dimensions %dx%d", ultrahdr::kMinWidth, ultrahdr::kMinHeight, img->w, img->h); } else if ((int)img->w > ultrahdr::kMaxWidth || (int)img->h > ultrahdr::kMaxHeight) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image dimensions cannot be larger than %dx%d, received image dimensions %dx%d", ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, img->w, img->h); } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_UV] == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for data field(s), luma ptr %p, chroma_uv ptr %p", img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_UV]); } else if (img->stride[UHDR_PLANE_Y] < img->w) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "luma stride must not be smaller than width, stride=%d, width=%d", img->stride[UHDR_PLANE_Y], img->w); } else if (img->stride[UHDR_PLANE_UV] < img->w) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "chroma_uv stride must not be smaller than width, stride=%d, width=%d", img->stride[UHDR_PLANE_UV], img->w); } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010 && (img->range != UHDR_CR_FULL_RANGE && img->range != UHDR_CR_LIMITED_RANGE)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid range, expects one of {UHDR_CR_FULL_RANGE, UHDR_CR_LIMITED_RANGE}"); } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 && img->range != UHDR_CR_FULL_RANGE) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid range, expects one of {UHDR_CR_FULL_RANGE}"); } } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) { if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_U] == nullptr || img->planes[UHDR_PLANE_V] == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for data field(s) luma ptr %p, chroma_u ptr %p, chroma_v ptr %p", img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_U], img->planes[UHDR_PLANE_V]); } else if (img->stride[UHDR_PLANE_Y] < img->w) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "luma stride must not be smaller than width, stride=%d, width=%d", img->stride[UHDR_PLANE_Y], img->w); } else if (img->stride[UHDR_PLANE_U] < img->w / 2) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "chroma_u stride must not be smaller than width / 2, stride=%d, width=%d", img->stride[UHDR_PLANE_U], img->w); } else if (img->stride[UHDR_PLANE_V] < img->w / 2) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "chroma_v stride must not be smaller than width / 2, stride=%d, width=%d", img->stride[UHDR_PLANE_V], img->w); } else if (img->range != UHDR_CR_FULL_RANGE) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid range, expects one of {UHDR_CR_FULL_RANGE}"); } } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || img->fmt == UHDR_IMG_FMT_32bppRGBA8888 || img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { if (img->planes[UHDR_PLANE_PACKED] == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for data field(s) rgb plane packed ptr %p", img->planes[UHDR_PLANE_PACKED]); } else if (img->stride[UHDR_PLANE_PACKED] < img->w) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "rgb planar stride must not be smaller than width, stride=%d, width=%d", img->stride[UHDR_PLANE_PACKED], img->w); } else if (img->range != UHDR_CR_FULL_RANGE) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid range, expects one of {UHDR_CR_FULL_RANGE}"); } } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); if (intent == UHDR_HDR_IMG && handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) { auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second; if (img->w != sdr_raw_entry->w || img->h != sdr_raw_entry->h) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image resolutions mismatch: hdr intent: %dx%d, sdr intent: %dx%d", img->w, img->h, sdr_raw_entry->w, sdr_raw_entry->h); return status; } } if (intent == UHDR_SDR_IMG && handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) { auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second; if (img->w != hdr_raw_entry->w || img->h != hdr_raw_entry->h) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image resolutions mismatch: sdr intent: %dx%d, hdr intent: %dx%d", img->w, img->h, hdr_raw_entry->w, hdr_raw_entry->h); return status; } } if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } std::unique_ptr entry = ultrahdr::copy_raw_image(img); if (entry == nullptr) { status.error_code = UHDR_CODEC_UNKNOWN_ERROR; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "encountered unknown error during color space conversion"); return status; } handle->m_raw_images.insert_or_assign(intent, std::move(entry)); return status; } uhdr_error_info_t uhdr_enc_set_compressed_image(uhdr_codec_private_t* enc, uhdr_compressed_image_t* img, uhdr_img_label_t intent) { uhdr_error_info_t status = g_no_error; if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG}", intent); } return uhdr_enc_validate_and_set_compressed_img(enc, img, intent); } uhdr_error_info_t uhdr_enc_set_gainmap_image(uhdr_codec_private_t* enc, uhdr_compressed_image_t* img, uhdr_gainmap_metadata_t* metadata) { uhdr_error_info_t status = ultrahdr::uhdr_validate_gainmap_metadata_descriptor(metadata); if (status.error_code != UHDR_CODEC_OK) return status; status = uhdr_enc_validate_and_set_compressed_img(enc, img, UHDR_GAIN_MAP_IMG); if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); memcpy(&handle->m_metadata, metadata, sizeof *metadata); return status; } uhdr_error_info_t uhdr_enc_set_quality(uhdr_codec_private_t* enc, int quality, uhdr_img_label_t intent) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (quality < 0 || quality > 100) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid quality factor %d, expects in range [0-100]", quality); } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG && intent != UHDR_GAIN_MAP_IMG) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG, " "UHDR_GAIN_MAP_IMG}", intent); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_quality.insert_or_assign(intent, quality); return status; } uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc, uhdr_mem_block_t* exif) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (exif == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for exif image handle"); } else if (exif->data == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for exif->data field"); } else if (exif->capacity < exif->data_sz) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "exif->capacity %zd is less than exif->data_sz %zd", exif->capacity, exif->data_sz); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } uint8_t* data = static_cast(exif->data); std::vector entry(data, data + exif->data_sz); handle->m_exif = std::move(entry); return status; } uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* enc, uhdr_codec_t media_type) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (media_type != UHDR_CODEC_JPG) { status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid output format %d, expects {UHDR_CODEC_JPG}", media_type); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_encode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_output_format = media_type; return status; } uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) { if (dynamic_cast(enc) == nullptr) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } uhdr_encoder_private* handle = dynamic_cast(enc); if (handle->m_sailed) { return handle->m_encode_call_status; } handle->m_sailed = true; uhdr_error_info_t& status = handle->m_encode_call_status; if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() && handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) { if (handle->m_effects.size() != 0) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image effects are not enabled for inputs with compressed intent"); return status; } } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) { if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() && handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { // api - 0 if (handle->m_effects.size() != 0) { status = ultrahdr::apply_effects(handle); if (status.error_code != UHDR_CODEC_OK) return status; } } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) != handle->m_compressed_images.end() && handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { if (handle->m_effects.size() != 0) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image effects are not enabled for inputs with compressed intent"); return status; } } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) { if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) { if (handle->m_effects.size() != 0) { status = ultrahdr::apply_effects(handle); if (status.error_code != UHDR_CODEC_OK) return status; } } else { if (handle->m_effects.size() != 0) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "image effects are not enabled for inputs with compressed intent"); return status; } } } } if (handle->m_output_format == UHDR_CODEC_JPG) { uhdr_mem_block_t exif{}; if (handle->m_exif.size() > 0) { exif.data = handle->m_exif.data(); exif.capacity = exif.data_sz = handle->m_exif.size(); } ultrahdr::JpegR jpegr(nullptr, handle->m_gainmap_scale_factor, handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second, handle->m_use_multi_channel_gainmap, handle->m_gamma, handle->m_enc_preset, handle->m_min_content_boost, handle->m_max_content_boost, handle->m_target_disp_max_brightness); if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() && handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) { auto& base_entry = handle->m_compressed_images.find(UHDR_BASE_IMG)->second; auto& gainmap_entry = handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG)->second; size_t size = (std::max)(((size_t)8 * 1024), 2 * (base_entry->data_sz + gainmap_entry->data_sz)); handle->m_compressed_output_buffer = std::make_unique( UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size); ultrahdr::uhdr_gainmap_metadata_ext_t metadata(handle->m_metadata, ultrahdr::kJpegrVersion); // api - 4 status = jpegr.encodeJPEGR(base_entry.get(), gainmap_entry.get(), &metadata, handle->m_compressed_output_buffer.get()); } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) { auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second; size_t size = (std::max)((8u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2); handle->m_compressed_output_buffer = std::make_unique( UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size); if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() && handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { // api - 0 status = jpegr.encodeJPEGR(hdr_raw_entry.get(), handle->m_compressed_output_buffer.get(), handle->m_quality.find(UHDR_BASE_IMG)->second, handle->m_exif.size() > 0 ? &exif : nullptr); } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) != handle->m_compressed_images.end() && handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second; // api - 3 status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_compressed_entry.get(), handle->m_compressed_output_buffer.get()); } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) { auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second; if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) { // api - 1 status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_raw_entry.get(), handle->m_compressed_output_buffer.get(), handle->m_quality.find(UHDR_BASE_IMG)->second, handle->m_exif.size() > 0 ? &exif : nullptr); } else { auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second; // api - 2 status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_raw_entry.get(), sdr_compressed_entry.get(), handle->m_compressed_output_buffer.get()); } } } else { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "resources required for uhdr_encode() operation are not present"); } } return status; } uhdr_compressed_image_t* uhdr_get_encoded_stream(uhdr_codec_private_t* enc) { if (dynamic_cast(enc) == nullptr) { return nullptr; } uhdr_encoder_private* handle = dynamic_cast(enc); if (!handle->m_sailed || handle->m_encode_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return handle->m_compressed_output_buffer.get(); } void uhdr_reset_encoder(uhdr_codec_private_t* enc) { if (dynamic_cast(enc) != nullptr) { uhdr_encoder_private* handle = dynamic_cast(enc); // clear entries and restore defaults for (auto it : handle->m_effects) delete it; handle->m_effects.clear(); #ifdef UHDR_ENABLE_GLES handle->m_uhdr_gl_ctxt.reset_opengl_ctxt(); handle->m_enable_gles = false; #endif handle->m_sailed = false; handle->m_raw_images.clear(); handle->m_compressed_images.clear(); handle->m_quality.clear(); handle->m_quality.emplace(UHDR_HDR_IMG, ultrahdr::kBaseCompressQualityDefault); handle->m_quality.emplace(UHDR_SDR_IMG, ultrahdr::kBaseCompressQualityDefault); handle->m_quality.emplace(UHDR_BASE_IMG, ultrahdr::kBaseCompressQualityDefault); handle->m_quality.emplace(UHDR_GAIN_MAP_IMG, ultrahdr::kMapCompressQualityDefault); handle->m_exif.clear(); handle->m_output_format = UHDR_CODEC_JPG; handle->m_gainmap_scale_factor = ultrahdr::kMapDimensionScaleFactorDefault; handle->m_use_multi_channel_gainmap = ultrahdr::kUseMultiChannelGainMapDefault; handle->m_gamma = ultrahdr::kGainMapGammaDefault; handle->m_enc_preset = ultrahdr::kEncSpeedPresetDefault; handle->m_min_content_boost = FLT_MIN; handle->m_max_content_boost = FLT_MAX; handle->m_target_disp_max_brightness = -1.0f; handle->m_compressed_output_buffer.reset(); handle->m_encode_call_status = g_no_error; } } int is_uhdr_image(void* data, int size) { #define RET_IF_ERR(x) \ { \ uhdr_error_info_t status = (x); \ if (status.error_code != UHDR_CODEC_OK) { \ uhdr_release_decoder(obj); \ return 0; \ } \ } uhdr_codec_private_t* obj = uhdr_create_decoder(); uhdr_compressed_image_t uhdr_image; uhdr_image.data = data; uhdr_image.data_sz = size; uhdr_image.capacity = size; uhdr_image.cg = UHDR_CG_UNSPECIFIED; uhdr_image.ct = UHDR_CT_UNSPECIFIED; uhdr_image.range = UHDR_CR_UNSPECIFIED; RET_IF_ERR(uhdr_dec_set_image(obj, &uhdr_image)); RET_IF_ERR(uhdr_dec_probe(obj)); #undef RET_IF_ERR uhdr_release_decoder(obj); return 1; } uhdr_codec_private_t* uhdr_create_decoder(void) { uhdr_decoder_private* handle = new uhdr_decoder_private(); if (handle != nullptr) { uhdr_reset_decoder(handle); } return handle; } void uhdr_release_decoder(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) != nullptr) { uhdr_decoder_private* handle = dynamic_cast(dec); delete handle; } } uhdr_error_info_t uhdr_dec_set_image(uhdr_codec_private_t* dec, uhdr_compressed_image_t* img) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(dec) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (img == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle"); } else if (img->data == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for compressed img->data field"); } else if (img->capacity < img->data_sz) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "img->capacity %zd is less than img->data_sz %zd", img->capacity, img->data_sz); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_decoder_private* handle = dynamic_cast(dec); if (handle->m_probed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_decode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_uhdr_compressed_img = std::make_unique( img->cg, img->ct, img->range, img->data_sz); memcpy(handle->m_uhdr_compressed_img->data, img->data, img->data_sz); handle->m_uhdr_compressed_img->data_sz = img->data_sz; return status; } uhdr_error_info_t uhdr_dec_set_out_img_format(uhdr_codec_private_t* dec, uhdr_img_fmt_t fmt) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(dec) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (fmt != UHDR_IMG_FMT_32bppRGBA8888 && fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat && fmt != UHDR_IMG_FMT_32bppRGBA1010102) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid output format %d, expects one of {UHDR_IMG_FMT_32bppRGBA8888, " "UHDR_IMG_FMT_64bppRGBAHalfFloat, UHDR_IMG_FMT_32bppRGBA1010102}", fmt); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_decoder_private* handle = dynamic_cast(dec); if (handle->m_probed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_decode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_output_fmt = fmt; return status; } uhdr_error_info_t uhdr_dec_set_out_color_transfer(uhdr_codec_private_t* dec, uhdr_color_transfer_t ct) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(dec) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (ct != UHDR_CT_HLG && ct != UHDR_CT_PQ && ct != UHDR_CT_LINEAR && ct != UHDR_CT_SRGB) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid output color transfer %d, expects one of {UHDR_CT_HLG, UHDR_CT_PQ, " "UHDR_CT_LINEAR, UHDR_CT_SRGB}", ct); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_decoder_private* handle = dynamic_cast(dec); if (handle->m_probed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_decode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_output_ct = ct; return status; } uhdr_error_info_t uhdr_dec_set_out_max_display_boost(uhdr_codec_private_t* dec, float display_boost) { uhdr_error_info_t status = g_no_error; if (dynamic_cast(dec) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); } else if (!std::isfinite(display_boost) || display_boost < 1.0f) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid display boost %f, expects to be >= 1.0f}", display_boost); } if (status.error_code != UHDR_CODEC_OK) return status; uhdr_decoder_private* handle = dynamic_cast(dec); if (handle->m_probed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "An earlier call to uhdr_decode() has switched the context from configurable state to " "end state. The context is no longer configurable. To reuse, call reset()"); return status; } handle->m_output_max_disp_boost = display_boost; return status; } uhdr_error_info_t uhdr_dec_probe(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } uhdr_decoder_private* handle = dynamic_cast(dec); uhdr_error_info_t& status = handle->m_probe_call_status; if (!handle->m_probed) { handle->m_probed = true; if (handle->m_uhdr_compressed_img.get() == nullptr) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "did not receive any image for decoding"); return status; } ultrahdr::jpeg_info_struct primary_image; ultrahdr::jpeg_info_struct gainmap_image; ultrahdr::jpegr_info_struct jpegr_info; jpegr_info.primaryImgInfo = &primary_image; jpegr_info.gainmapImgInfo = &gainmap_image; ultrahdr::JpegR jpegr; status = jpegr.getJPEGRInfo(handle->m_uhdr_compressed_img.get(), &jpegr_info); if (status.error_code != UHDR_CODEC_OK) return status; ultrahdr::uhdr_gainmap_metadata_ext_t metadata; status = jpegr.parseGainMapMetadata(gainmap_image.isoData.data(), gainmap_image.isoData.size(), gainmap_image.xmpData.data(), gainmap_image.xmpData.size(), &metadata); if (status.error_code != UHDR_CODEC_OK) return status; handle->m_metadata.max_content_boost = metadata.max_content_boost; handle->m_metadata.min_content_boost = metadata.min_content_boost; handle->m_metadata.gamma = metadata.gamma; handle->m_metadata.offset_sdr = metadata.offset_sdr; handle->m_metadata.offset_hdr = metadata.offset_hdr; handle->m_metadata.hdr_capacity_min = metadata.hdr_capacity_min; handle->m_metadata.hdr_capacity_max = metadata.hdr_capacity_max; handle->m_img_wd = primary_image.width; handle->m_img_ht = primary_image.height; handle->m_gainmap_wd = gainmap_image.width; handle->m_gainmap_ht = gainmap_image.height; handle->m_gainmap_num_comp = gainmap_image.numComponents; handle->m_exif = std::move(primary_image.exifData); handle->m_exif_block.data = handle->m_exif.data(); handle->m_exif_block.data_sz = handle->m_exif_block.capacity = handle->m_exif.size(); handle->m_icc = std::move(primary_image.iccData); handle->m_icc_block.data = handle->m_icc.data(); handle->m_icc_block.data_sz = handle->m_icc_block.capacity = handle->m_icc.size(); handle->m_base_img = std::move(primary_image.imgData); handle->m_base_img_block.data = handle->m_base_img.data(); handle->m_base_img_block.data_sz = handle->m_base_img_block.capacity = handle->m_base_img.size(); handle->m_gainmap_img = std::move(gainmap_image.imgData); handle->m_gainmap_img_block.data = handle->m_gainmap_img.data(); handle->m_gainmap_img_block.data_sz = handle->m_gainmap_img_block.capacity = handle->m_gainmap_img.size(); } return status; } int uhdr_dec_get_image_width(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return -1; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return -1; } return handle->m_img_wd; } int uhdr_dec_get_image_height(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return -1; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return -1; } return handle->m_img_ht; } int uhdr_dec_get_gainmap_width(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return -1; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return -1; } return handle->m_gainmap_wd; } int uhdr_dec_get_gainmap_height(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return -1; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return -1; } return handle->m_gainmap_ht; } uhdr_mem_block_t* uhdr_dec_get_exif(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return &handle->m_exif_block; } uhdr_mem_block_t* uhdr_dec_get_icc(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return &handle->m_icc_block; } uhdr_mem_block_t* uhdr_dec_get_base_image(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return &handle->m_base_img_block; } uhdr_mem_block_t* uhdr_dec_get_gainmap_image(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return &handle->m_gainmap_img_block; } uhdr_gainmap_metadata_t* uhdr_dec_get_gainmap_metadata(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return &handle->m_metadata; } uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } uhdr_decoder_private* handle = dynamic_cast(dec); if (handle->m_sailed) { return handle->m_decode_call_status; } uhdr_error_info_t& status = handle->m_decode_call_status; status = uhdr_dec_probe(dec); if (status.error_code != UHDR_CODEC_OK) return status; handle->m_sailed = true; if ((handle->m_output_fmt == UHDR_IMG_FMT_32bppRGBA1010102 && (handle->m_output_ct != UHDR_CT_HLG && handle->m_output_ct != UHDR_CT_PQ)) || (handle->m_output_fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat && handle->m_output_ct != UHDR_CT_LINEAR) || (handle->m_output_fmt == UHDR_IMG_FMT_32bppRGBA8888 && handle->m_output_ct != UHDR_CT_SRGB)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unsupported output pixel format and output color transfer pair"); return status; } handle->m_decoded_img_buffer = std::make_unique( handle->m_output_fmt, UHDR_CG_UNSPECIFIED, handle->m_output_ct, UHDR_CR_UNSPECIFIED, handle->m_img_wd, handle->m_img_ht, 1); handle->m_gainmap_img_buffer = std::make_unique( handle->m_gainmap_num_comp == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_32bppRGBA8888, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, handle->m_gainmap_wd, handle->m_gainmap_ht, 1); #ifdef UHDR_ENABLE_GLES ultrahdr::uhdr_opengl_ctxt_t* uhdrGLESCtxt = nullptr; if (handle->m_enable_gles && (handle->m_output_ct != UHDR_CT_SRGB || handle->m_effects.size() > 0)) { handle->m_uhdr_gl_ctxt.init_opengl_ctxt(); status = handle->m_uhdr_gl_ctxt.mErrorStatus; if (status.error_code != UHDR_CODEC_OK) return status; uhdrGLESCtxt = &handle->m_uhdr_gl_ctxt; } ultrahdr::JpegR jpegr(uhdrGLESCtxt); #else ultrahdr::JpegR jpegr; #endif status = jpegr.decodeJPEGR(handle->m_uhdr_compressed_img.get(), handle->m_decoded_img_buffer.get(), handle->m_output_max_disp_boost, handle->m_output_ct, handle->m_output_fmt, handle->m_gainmap_img_buffer.get(), nullptr); if (status.error_code == UHDR_CODEC_OK && dec->m_effects.size() != 0) { status = ultrahdr::apply_effects(handle); } #ifdef UHDR_ENABLE_GLES if (handle->m_enable_gles) { if (handle->m_uhdr_gl_ctxt.mDecodedImgTexture != 0) { handle->m_uhdr_gl_ctxt.read_texture( &handle->m_uhdr_gl_ctxt.mDecodedImgTexture, handle->m_decoded_img_buffer->fmt, handle->m_decoded_img_buffer->w, handle->m_decoded_img_buffer->h, handle->m_decoded_img_buffer->planes[0]); } if (handle->m_uhdr_gl_ctxt.mGainmapImgTexture != 0 && dec->m_effects.size() != 0) { handle->m_uhdr_gl_ctxt.read_texture( &handle->m_uhdr_gl_ctxt.mGainmapImgTexture, handle->m_gainmap_img_buffer->fmt, handle->m_gainmap_img_buffer->w, handle->m_gainmap_img_buffer->h, handle->m_gainmap_img_buffer->planes[0]); } } #endif return status; } uhdr_raw_image_t* uhdr_get_decoded_image(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return handle->m_decoded_img_buffer.get(); } uhdr_raw_image_t* uhdr_get_decoded_gainmap_image(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) == nullptr) { return nullptr; } uhdr_decoder_private* handle = dynamic_cast(dec); if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) { return nullptr; } return handle->m_gainmap_img_buffer.get(); } void uhdr_reset_decoder(uhdr_codec_private_t* dec) { if (dynamic_cast(dec) != nullptr) { uhdr_decoder_private* handle = dynamic_cast(dec); // clear entries and restore defaults for (auto it : handle->m_effects) delete it; handle->m_effects.clear(); #ifdef UHDR_ENABLE_GLES handle->m_uhdr_gl_ctxt.reset_opengl_ctxt(); handle->m_enable_gles = false; #endif handle->m_sailed = false; handle->m_uhdr_compressed_img.reset(); handle->m_output_fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat; handle->m_output_ct = UHDR_CT_LINEAR; handle->m_output_max_disp_boost = FLT_MAX; // ready to be configured handle->m_probed = false; handle->m_decoded_img_buffer.reset(); handle->m_gainmap_img_buffer.reset(); handle->m_img_wd = 0; handle->m_img_ht = 0; handle->m_gainmap_wd = 0; handle->m_gainmap_ht = 0; handle->m_gainmap_num_comp = 0; handle->m_exif.clear(); memset(&handle->m_exif_block, 0, sizeof handle->m_exif_block); handle->m_icc.clear(); memset(&handle->m_icc_block, 0, sizeof handle->m_icc_block); handle->m_base_img.clear(); memset(&handle->m_base_img_block, 0, sizeof handle->m_base_img_block); handle->m_gainmap_img.clear(); memset(&handle->m_gainmap_img_block, 0, sizeof handle->m_gainmap_img_block); memset(&handle->m_metadata, 0, sizeof handle->m_metadata); handle->m_probe_call_status = g_no_error; handle->m_decode_call_status = g_no_error; } } uhdr_error_info_t uhdr_enable_gpu_acceleration(uhdr_codec_private_t* codec, [[maybe_unused]] int enable) { uhdr_error_info_t status = g_no_error; if (codec == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (codec->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable " "state to end state. The context is no longer configurable. To reuse, call reset()"); return status; } #ifdef UHDR_ENABLE_GLES codec->m_enable_gles = enable; #endif return status; } uhdr_error_info_t uhdr_add_effect_mirror(uhdr_codec_private_t* codec, uhdr_mirror_direction_t direction) { uhdr_error_info_t status = g_no_error; if (codec == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (direction != UHDR_MIRROR_HORIZONTAL && direction != UHDR_MIRROR_VERTICAL) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "unsupported direction, expects one of {UHDR_MIRROR_HORIZONTAL, UHDR_MIRROR_VERTICAL}"); return status; } if (codec->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable " "state to end state. The context is no longer configurable. To reuse, call reset()"); return status; } codec->m_effects.push_back(new ultrahdr::uhdr_mirror_effect_t(direction)); return status; } uhdr_error_info_t uhdr_add_effect_rotate(uhdr_codec_private_t* codec, int degrees) { uhdr_error_info_t status = g_no_error; if (codec == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (degrees != 90 && degrees != 180 && degrees != 270) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "unsupported degrees, expects one of {90, 180, 270}"); return status; } if (codec->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable " "state to end state. The context is no longer configurable. To reuse, call reset()"); return status; } codec->m_effects.push_back(new ultrahdr::uhdr_rotate_effect_t(degrees)); return status; } uhdr_error_info_t uhdr_add_effect_crop(uhdr_codec_private_t* codec, int left, int right, int top, int bottom) { uhdr_error_info_t status = g_no_error; if (codec == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (codec->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable " "state to end state. The context is no longer configurable. To reuse, call reset()"); return status; } codec->m_effects.push_back(new ultrahdr::uhdr_crop_effect_t(left, right, top, bottom)); return status; } uhdr_error_info_t uhdr_add_effect_resize(uhdr_codec_private_t* codec, int width, int height) { uhdr_error_info_t status = g_no_error; if (codec == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } if (codec->m_sailed) { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf( status.detail, sizeof status.detail, "An earlier call to uhdr_encode()/uhdr_decode() has switched the context from configurable " "state to end state. The context is no longer configurable. To reuse, call reset()"); return status; } codec->m_effects.push_back(new ultrahdr::uhdr_resize_effect_t(width, height)); return status; }