diff --git a/examples/metadata.cfg b/examples/metadata.cfg index 8f9e23e..ad88985 100644 --- a/examples/metadata.cfg +++ b/examples/metadata.cfg @@ -1,8 +1,8 @@ ---maxContentBoost 6.0 ---minContentBoost 1.0 ---gamma 1.0 ---offsetSdr 0.0 ---offsetHdr 0.0 ---hdrCapacityMin 1.0 ---hdrCapacityMax 6.0 ---useBaseColorSpace 1 +--maxContentBoost 264.18 22.2026 857.325 +--minContentBoost 1 1.30101 1 +--gamma 1 1 1 +--offsetSdr 1e-07 1e-07 1e-07 +--offsetHdr 1e-07 1e-07 1e-07 +--hdrCapacityMin 1 +--hdrCapacityMax 49.2611 +--useBaseColorSpace 1 \ No newline at end of file diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp index db8c36a..3f03205 100644 --- a/examples/ultrahdr_app.cpp +++ b/examples/ultrahdr_app.cpp @@ -563,15 +563,15 @@ bool UltraHdrAppInput::fillGainMapCompressedImageHandle() { void parse_argument(uhdr_gainmap_metadata* metadata, char* argument, float* value) { if (!strcmp(argument, "maxContentBoost")) - metadata->max_content_boost = *value; + std::copy(value, value + 3, metadata->max_content_boost); else if (!strcmp(argument, "minContentBoost")) - metadata->min_content_boost = *value; + std::copy(value, value + 3, metadata->min_content_boost); else if (!strcmp(argument, "gamma")) - metadata->gamma = *value; + std::copy(value, value + 3, metadata->gamma); else if (!strcmp(argument, "offsetSdr")) - metadata->offset_sdr = *value; + std::copy(value, value + 3, metadata->offset_sdr); else if (!strcmp(argument, "offsetHdr")) - metadata->offset_hdr = *value; + std::copy(value, value + 3, metadata->offset_hdr); else if (!strcmp(argument, "hdrCapacityMin")) metadata->hdr_capacity_min = *value; else if (!strcmp(argument, "hdrCapacityMax")) @@ -589,11 +589,14 @@ bool UltraHdrAppInput::fillGainMapMetadataDescriptor() { } std::string line; char argument[128]; - float value; + float value[3]; while (std::getline(file, line)) { - if (sscanf(line.c_str(), "--%s %f", argument, &value) == 2) { - parse_argument(&mGainMapMetadata, argument, &value); - } + int count = sscanf(line.c_str(), "--%s %f %f %f", argument, &value[0], &value[1], &value[2]); + if (count == 2) value[1] = value[2] = value[0]; + if (count == 2 || count == 4) + parse_argument(&mGainMapMetadata, argument, value); + else + std::cout << " Ignoring line " << line << std::endl; } file.close(); return true; @@ -614,11 +617,34 @@ bool UltraHdrAppInput::writeGainMapMetadataToFile(uhdr_gainmap_metadata_t* metad if (!file.is_open()) { return false; } - file << "--maxContentBoost " << metadata->max_content_boost << std::endl; - file << "--minContentBoost " << metadata->min_content_boost << std::endl; - file << "--gamma " << metadata->gamma << std::endl; - file << "--offsetSdr " << metadata->offset_sdr << std::endl; - file << "--offsetHdr " << metadata->offset_hdr << std::endl; + bool allChannelsIdentical = metadata->max_content_boost[0] == metadata->max_content_boost[1] && + metadata->max_content_boost[0] == metadata->max_content_boost[2] && + metadata->min_content_boost[0] == metadata->min_content_boost[1] && + metadata->min_content_boost[0] == metadata->min_content_boost[2] && + metadata->gamma[0] == metadata->gamma[1] && + metadata->gamma[0] == metadata->gamma[2] && + metadata->offset_sdr[0] == metadata->offset_sdr[1] && + metadata->offset_sdr[0] == metadata->offset_sdr[2] && + metadata->offset_hdr[0] == metadata->offset_hdr[1] && + metadata->offset_hdr[0] == metadata->offset_hdr[2]; + if (allChannelsIdentical) { + file << "--maxContentBoost " << metadata->max_content_boost[0] << std::endl; + file << "--minContentBoost " << metadata->min_content_boost[0] << std::endl; + file << "--gamma " << metadata->gamma[0] << std::endl; + file << "--offsetSdr " << metadata->offset_sdr[0] << std::endl; + file << "--offsetHdr " << metadata->offset_hdr[0] << std::endl; + } else { + file << "--maxContentBoost " << metadata->max_content_boost[0] << " " + << metadata->max_content_boost[1] << " " << metadata->max_content_boost[2] << std::endl; + file << "--minContentBoost " << metadata->min_content_boost[0] << " " + << metadata->min_content_boost[1] << " " << metadata->min_content_boost[2] << std::endl; + file << "--gamma " << metadata->gamma[0] << " " << metadata->gamma[1] << " " + << metadata->gamma[2] << std::endl; + file << "--offsetSdr " << metadata->offset_sdr[0] << " " << metadata->offset_sdr[1] << " " + << metadata->offset_sdr[2] << std::endl; + file << "--offsetHdr " << metadata->offset_hdr[0] << " " << metadata->offset_hdr[1] << " " + << metadata->offset_hdr[2] << std::endl; + } file << "--hdrCapacityMin " << metadata->hdr_capacity_min << std::endl; file << "--hdrCapacityMax " << metadata->hdr_capacity_max << std::endl; file << "--useBaseColorSpace " << metadata->use_base_cg << std::endl; diff --git a/fuzzer/ultrahdr_enc_fuzzer.cpp b/fuzzer/ultrahdr_enc_fuzzer.cpp index becb370..914343d 100644 --- a/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -39,7 +39,7 @@ constexpr int kTfMax = UHDR_CT_SRGB; class UltraHdrEncFuzzer { public: - UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size) {}; + UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; void process(); template void fillBuffer(T* data, int width, int height, int stride); @@ -69,9 +69,11 @@ void UltraHdrEncFuzzer::fillBuffer(T* data, int width, int height, int stride) { void UltraHdrEncFuzzer::process() { if (mFdp.remaining_bytes()) { - struct uhdr_raw_image hdrImg{}; - struct uhdr_raw_image sdrImg{}; - struct uhdr_raw_image gainmapImg{}; + struct uhdr_raw_image hdrImg {}; + struct uhdr_raw_image sdrImg {}; + struct uhdr_raw_image gainmapImg {}; + + float maxBoost[3], minBoost[3], gamma[3], offsetSdr[3], offsetHdr[3]; // which encode api to select int muxSwitch = mFdp.ConsumeIntegralInRange(0, 4); @@ -129,12 +131,28 @@ void UltraHdrEncFuzzer::process() { // encoding speed preset auto enc_preset = mFdp.ConsumeBool() ? UHDR_USAGE_REALTIME : UHDR_USAGE_BEST_QUALITY; + bool are_all_channels_identical = mFdp.ConsumeBool(); + // gainmap metadata - auto minBoost = mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); - auto maxBoost = mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); - auto gamma = mFdp.ConsumeFloatingPointInRange(-1.0f, 5); - auto offsetSdr = mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); - auto offsetHdr = mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); + if (are_all_channels_identical) { + minBoost[0] = minBoost[1] = minBoost[2] = + mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); + maxBoost[0] = maxBoost[1] = maxBoost[2] = + mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); + gamma[0] = gamma[1] = gamma[2] = mFdp.ConsumeFloatingPointInRange(-1.0f, 5); + offsetSdr[0] = offsetSdr[1] = offsetSdr[2] = + mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); + offsetHdr[0] = offsetHdr[1] = offsetHdr[2] = + mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); + } else { + for (int i = 0; i < 3; i++) { + minBoost[i] = mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); + maxBoost[i] = mFdp.ConsumeFloatingPointInRange(-4.0f, 64.0f); + gamma[i] = mFdp.ConsumeFloatingPointInRange(-1.0f, 5); + offsetSdr[i] = mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); + offsetHdr[i] = mFdp.ConsumeFloatingPointInRange(-1.0f, 1.0f); + } + } auto minCapacity = mFdp.ConsumeFloatingPointInRange(-4.0f, 48.0f); auto maxCapacity = mFdp.ConsumeFloatingPointInRange(-4.0f, 48.0f); auto useBaseCg = mFdp.ConsumeBool(); @@ -194,10 +212,14 @@ void UltraHdrEncFuzzer::process() { ALOGV("base image quality %d ", (int)base_quality); ALOGV("encoding preset %d ", (int)enc_preset); ALOGV( - "gainmap metadata: min content boost %f, max content boost %f, gamma %f, offset sdr %f, " - "offset hdr %f, hdr min capacity %f, hdr max capacity %f, useBaseCg %d", - (float)minBoost, (float)maxBoost, (float)gamma, (float)offsetSdr, (float)offsetHdr, - (float)minCapacity, (float)maxCapacity, (int)useBaseCg); + "gainmap metadata: min content boost %f %f %f, max content boost %f %f %f, gamma %f %f %f, " + "offset sdr %f %f %f, offset hdr %f %f %f, hdr min capacity %f, hdr max capacity %f, " + "useBaseCg %d", + (float)minBoost[0], (float)minBoost[1], (float)minBoost[2], (float)maxBoost[0], + (float)maxBoost[1], (float)maxBoost[2], (float)gamma[0], (float)gamma[1], (float)gamma[2], + (float)offsetSdr[0], (float)offsetSdr[1], offsetSdr[2], (float)offsetHdr[0], + (float)offsetHdr[1], (float)offsetHdr[2], (float)minCapacity, (float)maxCapacity, + (int)useBaseCg); ALOGV("hdr intent luma stride %d, chroma stride %d", yHdrStride, uvHdrStride); ALOGV("sdr intent luma stride %d, chroma stride %d", ySdrStride, uvSdrStride); if (applyMirror) ALOGV("added mirror effect, direction %d", (int)direction); @@ -362,8 +384,8 @@ void UltraHdrEncFuzzer::process() { ON_ERR(uhdr_enc_set_exif_data(enc_handle, &exif)) ON_ERR(uhdr_enc_set_using_multi_channel_gainmap(enc_handle, multi_channel_gainmap)) ON_ERR(uhdr_enc_set_gainmap_scale_factor(enc_handle, gm_scale_factor)) - ON_ERR(uhdr_enc_set_gainmap_gamma(enc_handle, gamma)) - ON_ERR(uhdr_enc_set_min_max_content_boost(enc_handle, minBoost, maxBoost)) + ON_ERR(uhdr_enc_set_gainmap_gamma(enc_handle, gamma[0])) + ON_ERR(uhdr_enc_set_min_max_content_boost(enc_handle, minBoost[0], maxBoost[0])) ON_ERR(uhdr_enc_set_target_display_peak_brightness(enc_handle, targetDispPeakBrightness)) ON_ERR(uhdr_enc_set_preset(enc_handle, enc_preset)) ON_ERR(uhdr_enable_gpu_acceleration(enc_handle, 1)) @@ -393,11 +415,11 @@ void UltraHdrEncFuzzer::process() { UHDR_CODEC_OK) { struct uhdr_compressed_image jpegGainMap = gainMapEncoder.getCompressedImage(); uhdr_gainmap_metadata metadata; - metadata.max_content_boost = maxBoost; - metadata.min_content_boost = minBoost; - metadata.gamma = gamma; - metadata.offset_sdr = offsetSdr; - metadata.offset_hdr = offsetHdr; + std::copy(maxBoost, maxBoost + 3, metadata.max_content_boost); + std::copy(minBoost, minBoost + 3, metadata.min_content_boost); + std::copy(gamma, gamma + 3, metadata.gamma); + std::copy(offsetSdr, offsetSdr + 3, metadata.offset_sdr); + std::copy(offsetHdr, offsetHdr + 3, metadata.offset_hdr); metadata.hdr_capacity_min = minCapacity; metadata.hdr_capacity_max = maxCapacity; metadata.use_base_cg = useBaseCg; diff --git a/java/UltraHdrApp.java b/java/UltraHdrApp.java index cbb80d3..83f6b3b 100644 --- a/java/UltraHdrApp.java +++ b/java/UltraHdrApp.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; import com.google.media.codecs.ultrahdr.UltraHDRDecoder; import com.google.media.codecs.ultrahdr.UltraHDREncoder; @@ -278,33 +279,44 @@ public void fillGainMapMetadataDescriptor() throws IOException { String line; while ((line = reader.readLine()) != null) { String[] parts = line.split("\\s+"); - if (parts.length == 2 && parts[0].startsWith("--")) { + if (parts.length >= 2 && parts[0].startsWith("--")) { String option = parts[0].substring(2); // remove the "--" prefix - float value = Float.parseFloat(parts[1]); + float[] values = new float[3]; + int count = Math.min(parts.length - 1, 3); + if (count != 1 && count != 3) { + System.err.println("ignoring line: " + line); + continue; + } + for (int i = 0; i < count; i++) { + values[i] = Float.parseFloat(parts[i + 1]); + } + if (count == 1) { + values[1] = values[2] = values[0]; + } switch (option) { case "maxContentBoost": - mMetadata.maxContentBoost = value; + System.arraycopy(values, 0, mMetadata.maxContentBoost, 0, 3); break; case "minContentBoost": - mMetadata.minContentBoost = value; + System.arraycopy(values, 0, mMetadata.minContentBoost, 0, 3); break; case "gamma": - mMetadata.gamma = value; + System.arraycopy(values, 0, mMetadata.gamma, 0, 3); break; case "offsetSdr": - mMetadata.offsetSdr = value; + System.arraycopy(values, 0, mMetadata.offsetSdr, 0, 3); break; case "offsetHdr": - mMetadata.offsetHdr = value; + System.arraycopy(values, 0, mMetadata.offsetHdr, 0, 3); break; case "hdrCapacityMin": - mMetadata.hdrCapacityMin = value; + mMetadata.hdrCapacityMin = values[0]; break; case "hdrCapacityMax": - mMetadata.hdrCapacityMax = value; + mMetadata.hdrCapacityMax = values[0]; break; case "useBaseColorSpace": - mMetadata.useBaseColorSpace = value != 0.0f; + mMetadata.useBaseColorSpace = values[0] != 0.0f; break; default: System.err.println("ignoring option: " + option); @@ -319,11 +331,37 @@ public void fillGainMapMetadataDescriptor() throws IOException { public void writeGainMapMetadataToFile(GainMapMetadata metadata) throws IOException { try (BufferedWriter writer = new BufferedWriter(new FileWriter(mGainMapMetadaCfgFile))) { - writer.write("--maxContentBoost " + metadata.maxContentBoost + "\n"); - writer.write("--minContentBoost " + metadata.minContentBoost + "\n"); - writer.write("--gamma " + metadata.gamma + "\n"); - writer.write("--offsetSdr " + metadata.offsetSdr + "\n"); - writer.write("--offsetHdr " + metadata.offsetHdr + "\n"); + boolean allChannelsIdentical = + metadata.maxContentBoost[0] == metadata.maxContentBoost[1] + && metadata.maxContentBoost[0] == metadata.maxContentBoost[2] + && metadata.minContentBoost[0] == metadata.minContentBoost[1] + && metadata.minContentBoost[0] == metadata.minContentBoost[2] + && metadata.gamma[0] == metadata.gamma[1] + && metadata.gamma[0] == metadata.gamma[2] + && metadata.offsetSdr[0] == metadata.offsetSdr[1] + && metadata.offsetSdr[0] == metadata.offsetSdr[2] + && metadata.offsetHdr[0] == metadata.offsetHdr[1] + && metadata.offsetHdr[0] == metadata.offsetHdr[2]; + if (allChannelsIdentical) { + writer.write("--maxContentBoost " + metadata.maxContentBoost[0] + "\n"); + writer.write("--minContentBoost " + metadata.minContentBoost[0] + "\n"); + writer.write("--gamma " + metadata.gamma[0] + "\n"); + writer.write("--offsetSdr " + metadata.offsetSdr[0] + "\n"); + writer.write("--offsetHdr " + metadata.offsetHdr[0] + "\n"); + } else { + writer.write("--maxContentBoost " + metadata.maxContentBoost[0] + " " + + metadata.maxContentBoost[1] + " " + metadata.maxContentBoost[2] + "\n"); + writer.write("--minContentBoost " + metadata.minContentBoost[0] + " " + + metadata.minContentBoost[1] + " " + metadata.minContentBoost[2] + "\n"); + writer.write("--gamma " + metadata.gamma[0] + " " + metadata.gamma[1] + " " + + metadata.gamma[2] + "\n"); + writer.write( + "--offsetSdr " + metadata.offsetSdr[0] + " " + metadata.offsetSdr[1] + " " + + metadata.offsetSdr[2] + "\n"); + writer.write( + "--offsetHdr " + metadata.offsetHdr[0] + " " + metadata.offsetHdr[1] + " " + + metadata.offsetHdr[2] + "\n"); + } writer.write("--hdrCapacityMin " + metadata.hdrCapacityMin + "\n"); writer.write("--hdrCapacityMax " + metadata.hdrCapacityMax + "\n"); writer.write("--useBaseColorSpace " + (metadata.useBaseColorSpace ? "1" : "0") + "\n"); diff --git a/java/com/google/media/codecs/ultrahdr/UltraHDRDecoder.java b/java/com/google/media/codecs/ultrahdr/UltraHDRDecoder.java index 3b2f762..dcae4e7 100644 --- a/java/com/google/media/codecs/ultrahdr/UltraHDRDecoder.java +++ b/java/com/google/media/codecs/ultrahdr/UltraHDRDecoder.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; /** * Ultra HDR decoding utility class. @@ -38,34 +39,34 @@ public class UltraHDRDecoder implements AutoCloseable { * GainMap Metadata Descriptor */ public static class GainMapMetadata { - public float maxContentBoost; - public float minContentBoost; - public float gamma; - public float offsetSdr; - public float offsetHdr; + public float[] maxContentBoost = new float[3]; + public float[] minContentBoost = new float[3]; + public float[] gamma = new float[3]; + public float[] offsetSdr = new float[3]; + public float[] offsetHdr = new float[3]; public float hdrCapacityMin; public float hdrCapacityMax; public boolean useBaseColorSpace; public GainMapMetadata() { - this.maxContentBoost = 1.0f; - this.minContentBoost = 1.0f; - this.gamma = 1.0f; - this.offsetSdr = 0.0f; - this.offsetHdr = 0.0f; + Arrays.fill(this.maxContentBoost, 1.0f); + Arrays.fill(this.minContentBoost, 1.0f); + Arrays.fill(this.gamma, 1.0f); + Arrays.fill(this.offsetSdr, 0.0f); + Arrays.fill(this.offsetHdr, 0.0f); this.hdrCapacityMin = 1.0f; this.hdrCapacityMax = 1.0f; this.useBaseColorSpace = true; } - public GainMapMetadata(float maxContentBoost, float minContentBoost, float gamma, - float offsetSdr, float offsetHdr, float hdrCapacityMin, float hdrCapacityMax, + public GainMapMetadata(float[] maxContentBoost, float[] minContentBoost, float[] gamma, + float[] offsetSdr, float[] offsetHdr, float hdrCapacityMin, float hdrCapacityMax, boolean useBaseColorSpace) { - this.maxContentBoost = maxContentBoost; - this.minContentBoost = minContentBoost; - this.gamma = gamma; - this.offsetSdr = offsetSdr; - this.offsetHdr = offsetHdr; + System.arraycopy(maxContentBoost, 0, this.maxContentBoost, 0, 3); + System.arraycopy(minContentBoost, 0, this.minContentBoost, 0, 3); + System.arraycopy(gamma, 0, this.gamma, 0, 3); + System.arraycopy(offsetSdr, 0, this.offsetSdr, 0, 3); + System.arraycopy(offsetHdr, 0, this.offsetHdr, 0, 3); this.hdrCapacityMin = hdrCapacityMin; this.hdrCapacityMax = hdrCapacityMax; this.useBaseColorSpace = useBaseColorSpace; @@ -478,11 +479,11 @@ public void reset() throws IOException { } private void resetState() { - maxContentBoost = 1.0f; - minContentBoost = 1.0f; - gamma = 1.0f; - offsetSdr = 0.0f; - offsetHdr = 0.0f; + Arrays.fill(maxContentBoost, 1.0f); + Arrays.fill(minContentBoost, 1.0f); + Arrays.fill(gamma, 1.0f); + Arrays.fill(offsetSdr, 0.0f); + Arrays.fill(offsetHdr, 0.0f); hdrCapacityMin = 1.0f; hdrCapacityMax = 1.0f; useBaseColorSpace = true; @@ -559,11 +560,11 @@ private native void setCompressedImageNative(byte[] data, int size, int colorGam /** * gainmap metadata fields. Filled by {@link UltraHDRDecoder#getGainmapMetadataNative()} */ - private float maxContentBoost; - private float minContentBoost; - private float gamma; - private float offsetSdr; - private float offsetHdr; + private float[] maxContentBoost = new float[3]; + private float[] minContentBoost = new float[3]; + private float[] gamma = new float[3]; + private float[] offsetSdr = new float[3]; + private float[] offsetHdr = new float[3]; private float hdrCapacityMin; private float hdrCapacityMax; private boolean useBaseColorSpace; diff --git a/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java b/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java index 396ee56..ac77886 100644 --- a/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java +++ b/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java @@ -315,8 +315,8 @@ public void setCompressedImage(byte[] data, int size, int colorGamut, int colorT * or current encoder instance is not suitable for configuration * exception is thrown */ - public void setGainMapImageInfo(byte[] data, int size, float maxContentBoost, - float minContentBoost, float gainmapGamma, float offsetSdr, float offsetHdr, + public void setGainMapImageInfo(byte[] data, int size, float[] maxContentBoost, + float[] minContentBoost, float[] gainmapGamma, float[] offsetSdr, float[] offsetHdr, float hdrCapacityMin, float hdrCapacityMax, boolean useBaseColorSpace) throws IOException { if (data == null) { @@ -531,8 +531,8 @@ private native void setRawImageNative(byte[] yBuff, byte[] uBuff, byte[] vBuff, private native void setCompressedImageNative(byte[] data, int size, int colorGamut, int colorTransfer, int range, int intent) throws IOException; - private native void setGainMapImageInfoNative(byte[] data, int size, float maxContentBoost, - float minContentBoost, float gainmapGamma, float offsetSdr, float offsetHdr, + private native void setGainMapImageInfoNative(byte[] data, int size, float[] maxContentBoost, + float[] minContentBoost, float[] gainmapGamma, float[] offsetSdr, float[] offsetHdr, float hdrCapacityMin, float hdrCapacityMax, boolean useBaseColorSpace) throws IOException; diff --git a/java/jni/com_google_media_codecs_ultrahdr_UltraHDREncoder.h b/java/jni/com_google_media_codecs_ultrahdr_UltraHDREncoder.h index dc8a62f..271aa93 100644 --- a/java/jni/com_google_media_codecs_ultrahdr_UltraHDREncoder.h +++ b/java/jni/com_google_media_codecs_ultrahdr_UltraHDREncoder.h @@ -76,10 +76,10 @@ JNIEXPORT void JNICALL Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_set /* * Class: com_google_media_codecs_ultrahdr_UltraHDREncoder * Method: setGainMapImageInfoNative - * Signature: ([BIFFFFFFFZ)V + * Signature: ([BI[F[F[F[F[FFFZ)V */ JNIEXPORT void JNICALL Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setGainMapImageInfoNative - (JNIEnv *, jobject, jbyteArray, jint, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jboolean); + (JNIEnv *, jobject, jbyteArray, jint, jfloatArray, jfloatArray, jfloatArray, jfloatArray, jfloatArray, jfloat, jfloat, jboolean); /* * Class: com_google_media_codecs_ultrahdr_UltraHDREncoder diff --git a/java/jni/ultrahdr-jni.cpp b/java/jni/ultrahdr-jni.cpp index edc3fa8..e105b52 100644 --- a/java/jni/ultrahdr-jni.cpp +++ b/java/jni/ultrahdr-jni.cpp @@ -233,9 +233,10 @@ Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setCompressedImageNative( extern "C" JNIEXPORT void JNICALL Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setGainMapImageInfoNative( - JNIEnv *env, jobject thiz, jbyteArray data, jint size, jfloat max_content_boost, - jfloat min_content_boost, jfloat gainmap_gamma, jfloat offset_sdr, jfloat offset_hdr, - jfloat hdr_capacity_min, jfloat hdr_capacity_max, jboolean use_base_color_space) { + JNIEnv *env, jobject thiz, jbyteArray data, jint size, jfloatArray max_content_boost, + jfloatArray min_content_boost, jfloatArray gainmap_gamma, jfloatArray offset_sdr, + jfloatArray offset_hdr, jfloat hdr_capacity_min, jfloat hdr_capacity_max, + jboolean use_base_color_space) { GET_HANDLE() RET_IF_TRUE(handle == 0, "java/io/IOException", "invalid encoder instance") jsize length = env->GetArrayLength(data); @@ -248,9 +249,23 @@ Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setGainMapImageInfoNative( UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED}; - uhdr_gainmap_metadata_t metadata{max_content_boost, min_content_boost, gainmap_gamma, - offset_sdr, offset_hdr, hdr_capacity_min, - hdr_capacity_max, use_base_color_space}; + +#define GET_FLOAT_ARRAY(env, srcArray, dstArray) \ + { \ + RET_IF_TRUE(srcArray == nullptr, "java/io/IOException", "received nullptr"); \ + jsize length = env->GetArrayLength(srcArray); \ + RET_IF_TRUE(length != 3, "java/io/IOException", "array must have 3 elements"); \ + env->GetFloatArrayRegion(srcArray, 0, 3, dstArray); \ + } + uhdr_gainmap_metadata_t metadata{}; + GET_FLOAT_ARRAY(env, max_content_boost, metadata.max_content_boost) + GET_FLOAT_ARRAY(env, min_content_boost, metadata.min_content_boost) + GET_FLOAT_ARRAY(env, gainmap_gamma, metadata.gamma) + GET_FLOAT_ARRAY(env, offset_sdr, metadata.offset_sdr) + GET_FLOAT_ARRAY(env, offset_hdr, metadata.offset_hdr) + metadata.hdr_capacity_min = hdr_capacity_min; + metadata.hdr_capacity_max = hdr_capacity_max; + metadata.use_base_cg = use_base_color_space; auto status = uhdr_enc_set_gainmap_image((uhdr_codec_private_t *)handle, &img, &metadata); env->ReleaseByteArrayElements(data, body, 0); RET_IF_TRUE( @@ -624,6 +639,19 @@ Java_com_google_media_codecs_ultrahdr_UltraHDRDecoder_getGainmapMetadataNative(J uhdr_dec_get_gainmap_metadata((uhdr_codec_private_t *)handle); RET_IF_TRUE(gainmap_metadata == nullptr, "java/io/IOException", "uhdr_dec_probe() is not yet called or it has returned with error") +#define SET_FLOAT_ARRAY_FIELD(name, valArray) \ + { \ + jfieldID fID = env->GetFieldID(clazz, name, "[F"); \ + RET_IF_TRUE(fID == nullptr, "java/io/IOException", \ + "GetFieldID for field " #name " returned with error") \ + jfloatArray array = env->NewFloatArray(3); \ + RET_IF_TRUE(array == nullptr, "java/io/IOException", \ + "Failed to allocate float array for field " #name) \ + env->SetFloatArrayRegion(array, 0, 3, (const jfloat *)valArray); \ + env->SetObjectField(thiz, fID, array); \ + env->DeleteLocalRef(array); \ + } + #define SET_FLOAT_FIELD(name, val) \ { \ jfieldID fID = env->GetFieldID(clazz, name, "F"); \ @@ -631,11 +659,11 @@ Java_com_google_media_codecs_ultrahdr_UltraHDRDecoder_getGainmapMetadataNative(J "GetFieldID for field " #name " returned with error") \ env->SetFloatField(thiz, fID, (jfloat)val); \ } - SET_FLOAT_FIELD("maxContentBoost", gainmap_metadata->max_content_boost) - SET_FLOAT_FIELD("minContentBoost", gainmap_metadata->min_content_boost) - SET_FLOAT_FIELD("gamma", gainmap_metadata->gamma) - SET_FLOAT_FIELD("offsetSdr", gainmap_metadata->offset_sdr) - SET_FLOAT_FIELD("offsetHdr", gainmap_metadata->offset_hdr) + SET_FLOAT_ARRAY_FIELD("maxContentBoost", gainmap_metadata->max_content_boost) + SET_FLOAT_ARRAY_FIELD("minContentBoost", gainmap_metadata->min_content_boost) + SET_FLOAT_ARRAY_FIELD("gamma", gainmap_metadata->gamma) + SET_FLOAT_ARRAY_FIELD("offsetSdr", gainmap_metadata->offset_sdr) + SET_FLOAT_ARRAY_FIELD("offsetHdr", gainmap_metadata->offset_hdr) SET_FLOAT_FIELD("hdrCapacityMin", gainmap_metadata->hdr_capacity_min) SET_FLOAT_FIELD("hdrCapacityMax", gainmap_metadata->hdr_capacity_max) #define SET_BOOLEAN_FIELD(name, val) \ diff --git a/java/metadata.cfg b/java/metadata.cfg index 8f9e23e..ad88985 100644 --- a/java/metadata.cfg +++ b/java/metadata.cfg @@ -1,8 +1,8 @@ ---maxContentBoost 6.0 ---minContentBoost 1.0 ---gamma 1.0 ---offsetSdr 0.0 ---offsetHdr 0.0 ---hdrCapacityMin 1.0 ---hdrCapacityMax 6.0 ---useBaseColorSpace 1 +--maxContentBoost 264.18 22.2026 857.325 +--minContentBoost 1 1.30101 1 +--gamma 1 1 1 +--offsetSdr 1e-07 1e-07 1e-07 +--offsetHdr 1e-07 1e-07 1e-07 +--hdrCapacityMin 1 +--hdrCapacityMax 49.2611 +--useBaseColorSpace 1 \ No newline at end of file diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index 54a604a..b51a977 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -456,48 +456,57 @@ constexpr int32_t kGainFactorPrecision = 10; constexpr int32_t kGainFactorNumEntries = 1 << kGainFactorPrecision; struct GainLUT { - GainLUT(uhdr_gainmap_metadata_ext_t* metadata) { - this->mGammaInv = 1.0f / metadata->gamma; - for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->min_content_boost) * (1.0f - value) + - log2(metadata->max_content_boost) * value; - mGainTable[idx] = exp2(logBoost); + GainLUT(uhdr_gainmap_metadata_ext_t* metadata, float gainmapWeight) { + bool isSingleChannel = metadata->are_all_channels_identical(); + for (int i = 0; i < (isSingleChannel ? 1 : 3); i++) { + mGainTable[i] = memory[i] = new float[kGainFactorNumEntries]; + this->mGammaInv[i] = 1.0f / metadata->gamma[i]; + for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) { + float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); + float logBoost = log2(metadata->min_content_boost[i]) * (1.0f - value) + + log2(metadata->max_content_boost[i]) * value; + mGainTable[i][idx] = exp2(logBoost * gainmapWeight); + } + } + if (isSingleChannel) { + memory[1] = memory[2] = nullptr; + mGammaInv[1] = mGammaInv[2] = mGammaInv[0]; + mGainTable[1] = mGainTable[2] = mGainTable[0]; } } - GainLUT(uhdr_gainmap_metadata_ext_t* metadata, float gainmapWeight) { - this->mGammaInv = 1.0f / metadata->gamma; - for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) { - float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->min_content_boost) * (1.0f - value) + - log2(metadata->max_content_boost) * value; - mGainTable[idx] = exp2(logBoost * gainmapWeight); + GainLUT(uhdr_gainmap_metadata_ext_t* metadata) : GainLUT(metadata, 1.0f) {} + + ~GainLUT() { + for (int i = 0; i < 3; i++) { + if (memory[i]) { + delete[] memory[i]; + memory[i] = nullptr; + } } } - ~GainLUT() {} - - float getGainFactor(float gain) { - if (mGammaInv != 1.0f) gain = pow(gain, mGammaInv); + float getGainFactor(float gain, int index) { + if (mGammaInv[index] != 1.0f) gain = pow(gain, mGammaInv[index]); int32_t idx = static_cast(gain * (kGainFactorNumEntries - 1) + 0.5); // TODO() : Remove once conversion modules have appropriate clamping in place idx = CLIP3(idx, 0, kGainFactorNumEntries - 1); - return mGainTable[idx]; + return mGainTable[index][idx]; } private: - float mGainTable[kGainFactorNumEntries]; - float mGammaInv; + float* memory[3]{}; + float* mGainTable[3]{}; + float mGammaInv[3]{}; }; /* * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR * luminances in linear space and gainmap metadata fields. */ -uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata); +uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata, int index); uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata, - float log2MinContentBoost, float log2MaxContentBoost); + float log2MinContentBoost, float log2MaxContentBoost, int index); float computeGain(float sdr, float hdr); uint8_t affineMapGain(float gainlog2, float mingainlog2, float maxgainlog2, float gamma); diff --git a/lib/include/ultrahdr/ultrahdrcommon.h b/lib/include/ultrahdr/ultrahdrcommon.h index 8f22875..4823844 100644 --- a/lib/include/ultrahdr/ultrahdrcommon.h +++ b/lib/include/ultrahdr/ultrahdrcommon.h @@ -208,16 +208,26 @@ typedef struct uhdr_gainmap_metadata_ext : uhdr_gainmap_metadata { uhdr_gainmap_metadata_ext(uhdr_gainmap_metadata& metadata, std::string ver) : uhdr_gainmap_metadata_ext(ver) { - max_content_boost = metadata.max_content_boost; - min_content_boost = metadata.min_content_boost; - gamma = metadata.gamma; - offset_sdr = metadata.offset_sdr; - offset_hdr = metadata.offset_hdr; + std::copy(metadata.max_content_boost, metadata.max_content_boost + 3, max_content_boost); + std::copy(metadata.min_content_boost, metadata.min_content_boost + 3, min_content_boost); + std::copy(metadata.gamma, metadata.gamma + 3, gamma); + std::copy(metadata.offset_sdr, metadata.offset_sdr + 3, offset_sdr); + std::copy(metadata.offset_hdr, metadata.offset_hdr + 3, offset_hdr); hdr_capacity_min = metadata.hdr_capacity_min; hdr_capacity_max = metadata.hdr_capacity_max; use_base_cg = metadata.use_base_cg; } + bool are_all_channels_identical() const { + return max_content_boost[0] == max_content_boost[1] && + max_content_boost[0] == max_content_boost[2] && + min_content_boost[0] == min_content_boost[1] && + min_content_boost[0] == min_content_boost[2] && gamma[0] == gamma[1] && + gamma[0] == gamma[2] && offset_sdr[0] == offset_sdr[1] && + offset_sdr[0] == offset_sdr[2] && offset_hdr[0] == offset_hdr[1] && + offset_hdr[0] == offset_hdr[2]; + } + std::string version; /**< Ultra HDR format version */ } uhdr_gainmap_metadata_ext_t; /**< alias for struct uhdr_gainmap_metadata */ diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index 17f9df4..b14be0e 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -756,23 +756,23 @@ void transformYuv444(uhdr_raw_image_t* image, const std::array& coeffs //////////////////////////////////////////////////////////////////////////////// // Gain map calculations -uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata) { - return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->min_content_boost), - log2(metadata->max_content_boost)); +uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata, int index) { + return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->min_content_boost[index]), + log2(metadata->max_content_boost[index]), index); } uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata, - float log2MinContentBoost, float log2MaxContentBoost) { + float log2MinContentBoost, float log2MaxContentBoost, int index) { float gain = 1.0f; if (y_sdr > 0.0f) { gain = y_hdr / y_sdr; } - if (gain < metadata->min_content_boost) gain = metadata->min_content_boost; - if (gain > metadata->max_content_boost) gain = metadata->max_content_boost; + if (gain < metadata->min_content_boost[index]) gain = metadata->min_content_boost[index]; + if (gain > metadata->max_content_boost[index]) gain = metadata->max_content_boost[index]; float gain_normalized = (log2(gain) - log2MinContentBoost) / (log2MaxContentBoost - log2MinContentBoost); - float gain_normalized_gamma = powf(gain_normalized, metadata->gamma); + float gain_normalized_gamma = powf(gain_normalized, metadata->gamma[index]); return static_cast(gain_normalized_gamma * 255.0f); } @@ -795,73 +795,69 @@ uint8_t affineMapGain(float gainlog2, float mingainlog2, float maxgainlog2, floa } Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata) { - if (metadata->gamma != 1.0f) gain = pow(gain, 1.0f / metadata->gamma); - float logBoost = - log2(metadata->min_content_boost) * (1.0f - gain) + log2(metadata->max_content_boost) * gain; + if (metadata->gamma[0] != 1.0f) gain = pow(gain, 1.0f / metadata->gamma[0]); + float logBoost = log2(metadata->min_content_boost[0]) * (1.0f - gain) + + log2(metadata->max_content_boost[0]) * gain; float gainFactor = exp2(logBoost); - return ((e + metadata->offset_sdr) * gainFactor) - metadata->offset_hdr; + return ((e + metadata->offset_sdr[0]) * gainFactor) - metadata->offset_hdr[0]; } Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata, float gainmapWeight) { - if (metadata->gamma != 1.0f) gain = pow(gain, 1.0f / metadata->gamma); - float logBoost = - log2(metadata->min_content_boost) * (1.0f - gain) + log2(metadata->max_content_boost) * gain; + if (metadata->gamma[0] != 1.0f) gain = pow(gain, 1.0f / metadata->gamma[0]); + float logBoost = log2(metadata->min_content_boost[0]) * (1.0f - gain) + + log2(metadata->max_content_boost[0]) * gain; float gainFactor = exp2(logBoost * gainmapWeight); - return ((e + metadata->offset_sdr) * gainFactor) - metadata->offset_hdr; + return ((e + metadata->offset_sdr[0]) * gainFactor) - metadata->offset_hdr[0]; } Color applyGainLUT(Color e, float gain, GainLUT& gainLUT, uhdr_gainmap_metadata_ext_t* metadata) { - float gainFactor = gainLUT.getGainFactor(gain); - return ((e + metadata->offset_sdr) * gainFactor) - metadata->offset_hdr; + float gainFactor = gainLUT.getGainFactor(gain, 0); + return ((e + metadata->offset_sdr[0]) * gainFactor) - metadata->offset_hdr[0]; } Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata) { - if (metadata->gamma != 1.0f) { - gain.r = pow(gain.r, 1.0f / metadata->gamma); - gain.g = pow(gain.g, 1.0f / metadata->gamma); - gain.b = pow(gain.b, 1.0f / metadata->gamma); - } - float logBoostR = log2(metadata->min_content_boost) * (1.0f - gain.r) + - log2(metadata->max_content_boost) * gain.r; - float logBoostG = log2(metadata->min_content_boost) * (1.0f - gain.g) + - log2(metadata->max_content_boost) * gain.g; - float logBoostB = log2(metadata->min_content_boost) * (1.0f - gain.b) + - log2(metadata->max_content_boost) * gain.b; + if (metadata->gamma[0] != 1.0f) gain.r = pow(gain.r, 1.0f / metadata->gamma[0]); + if (metadata->gamma[1] != 1.0f) gain.g = pow(gain.g, 1.0f / metadata->gamma[1]); + if (metadata->gamma[2] != 1.0f) gain.b = pow(gain.b, 1.0f / metadata->gamma[2]); + float logBoostR = log2(metadata->min_content_boost[0]) * (1.0f - gain.r) + + log2(metadata->max_content_boost[0]) * gain.r; + float logBoostG = log2(metadata->min_content_boost[1]) * (1.0f - gain.g) + + log2(metadata->max_content_boost[1]) * gain.g; + float logBoostB = log2(metadata->min_content_boost[2]) * (1.0f - gain.b) + + log2(metadata->max_content_boost[2]) * gain.b; float gainFactorR = exp2(logBoostR); float gainFactorG = exp2(logBoostG); float gainFactorB = exp2(logBoostB); - return {{{((e.r + metadata->offset_sdr) * gainFactorR) - metadata->offset_hdr, - ((e.g + metadata->offset_sdr) * gainFactorG) - metadata->offset_hdr, - ((e.b + metadata->offset_sdr) * gainFactorB) - metadata->offset_hdr}}}; + return {{{((e.r + metadata->offset_sdr[0]) * gainFactorR) - metadata->offset_hdr[0], + ((e.g + metadata->offset_sdr[1]) * gainFactorG) - metadata->offset_hdr[1], + ((e.b + metadata->offset_sdr[2]) * gainFactorB) - metadata->offset_hdr[2]}}}; } Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata, float gainmapWeight) { - if (metadata->gamma != 1.0f) { - gain.r = pow(gain.r, 1.0f / metadata->gamma); - gain.g = pow(gain.g, 1.0f / metadata->gamma); - gain.b = pow(gain.b, 1.0f / metadata->gamma); - } - float logBoostR = log2(metadata->min_content_boost) * (1.0f - gain.r) + - log2(metadata->max_content_boost) * gain.r; - float logBoostG = log2(metadata->min_content_boost) * (1.0f - gain.g) + - log2(metadata->max_content_boost) * gain.g; - float logBoostB = log2(metadata->min_content_boost) * (1.0f - gain.b) + - log2(metadata->max_content_boost) * gain.b; + if (metadata->gamma[0] != 1.0f) gain.r = pow(gain.r, 1.0f / metadata->gamma[0]); + if (metadata->gamma[1] != 1.0f) gain.g = pow(gain.g, 1.0f / metadata->gamma[1]); + if (metadata->gamma[2] != 1.0f) gain.b = pow(gain.b, 1.0f / metadata->gamma[2]); + float logBoostR = log2(metadata->min_content_boost[0]) * (1.0f - gain.r) + + log2(metadata->max_content_boost[0]) * gain.r; + float logBoostG = log2(metadata->min_content_boost[1]) * (1.0f - gain.g) + + log2(metadata->max_content_boost[1]) * gain.g; + float logBoostB = log2(metadata->min_content_boost[2]) * (1.0f - gain.b) + + log2(metadata->max_content_boost[2]) * gain.b; float gainFactorR = exp2(logBoostR * gainmapWeight); float gainFactorG = exp2(logBoostG * gainmapWeight); float gainFactorB = exp2(logBoostB * gainmapWeight); - return {{{((e.r + metadata->offset_sdr) * gainFactorR) - metadata->offset_hdr, - ((e.g + metadata->offset_sdr) * gainFactorG) - metadata->offset_hdr, - ((e.b + metadata->offset_sdr) * gainFactorB) - metadata->offset_hdr}}}; + return {{{((e.r + metadata->offset_sdr[0]) * gainFactorR) - metadata->offset_hdr[0], + ((e.g + metadata->offset_sdr[1]) * gainFactorG) - metadata->offset_hdr[1], + ((e.b + metadata->offset_sdr[2]) * gainFactorB) - metadata->offset_hdr[2]}}}; } Color applyGainLUT(Color e, Color gain, GainLUT& gainLUT, uhdr_gainmap_metadata_ext_t* metadata) { - float gainFactorR = gainLUT.getGainFactor(gain.r); - float gainFactorG = gainLUT.getGainFactor(gain.g); - float gainFactorB = gainLUT.getGainFactor(gain.b); - return {{{((e.r + metadata->offset_sdr) * gainFactorR) - metadata->offset_hdr, - ((e.g + metadata->offset_sdr) * gainFactorG) - metadata->offset_hdr, - ((e.b + metadata->offset_sdr) * gainFactorB) - metadata->offset_hdr}}}; + float gainFactorR = gainLUT.getGainFactor(gain.r, 0); + float gainFactorG = gainLUT.getGainFactor(gain.g, 1); + float gainFactorB = gainLUT.getGainFactor(gain.b, 2); + return {{{((e.r + metadata->offset_sdr[0]) * gainFactorR) - metadata->offset_hdr[0], + ((e.g + metadata->offset_sdr[1]) * gainFactorG) - metadata->offset_hdr[1], + ((e.b + metadata->offset_sdr[2]) * gainFactorB) - metadata->offset_hdr[2]}}}; } // TODO: do we need something more clever for filtering either the map or images diff --git a/lib/src/gainmapmetadata.cpp b/lib/src/gainmapmetadata.cpp index 2516b11..3699a96 100644 --- a/lib/src/gainmapmetadata.cpp +++ b/lib/src/gainmapmetadata.cpp @@ -324,17 +324,6 @@ uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat( UHDR_CHECK_NON_ZERO(from->alternateOffsetD[i], "alternateOffset denominator"); } - // TODO: extend uhdr_gainmap_metadata_ext_t to cover multi-channel - if (!from->allChannelsIdentical()) { - uhdr_error_info_t status; - status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; - status.has_detail = 1; - snprintf(status.detail, sizeof status.detail, - "current implementation does not handle images with gainmap metadata different " - "across r/g/b channels"); - return status; - } - // jpeg supports only 8 bits per component, applying gainmap in inverse direction is unexpected if (from->backwardDirection) { uhdr_error_info_t status; @@ -345,14 +334,16 @@ uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat( } to->version = kJpegrVersion; - to->max_content_boost = exp2((float)from->gainMapMaxN[0] / from->gainMapMaxD[0]); - to->min_content_boost = exp2((float)from->gainMapMinN[0] / from->gainMapMinD[0]); + for (int i = 0; i < 3; i++) { + to->max_content_boost[i] = exp2((float)from->gainMapMaxN[i] / from->gainMapMaxD[i]); + to->min_content_boost[i] = exp2((float)from->gainMapMinN[i] / from->gainMapMinD[i]); - to->gamma = (float)from->gainMapGammaN[0] / from->gainMapGammaD[0]; + to->gamma[i] = (float)from->gainMapGammaN[i] / from->gainMapGammaD[i]; - // BaseRenditionIsHDR is false - to->offset_sdr = (float)from->baseOffsetN[0] / from->baseOffsetD[0]; - to->offset_hdr = (float)from->alternateOffsetN[0] / from->alternateOffsetD[0]; + // BaseRenditionIsHDR is false + to->offset_sdr[i] = (float)from->baseOffsetN[i] / from->baseOffsetD[i]; + to->offset_hdr[i] = (float)from->alternateOffsetN[i] / from->alternateOffsetD[i]; + } to->hdr_capacity_max = exp2((float)from->alternateHdrHeadroomN / from->alternateHdrHeadroomD); to->hdr_capacity_min = exp2((float)from->baseHdrHeadroomN / from->baseHdrHeadroomD); to->use_base_cg = from->useBaseColorSpace; @@ -396,28 +387,38 @@ uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction( return status; \ } - CONVERT_FLT_TO_SIGNED_FRACTION(log2(from->max_content_boost), &to->gainMapMaxN[0], - &to->gainMapMaxD[0]) - to->gainMapMaxN[2] = to->gainMapMaxN[1] = to->gainMapMaxN[0]; - to->gainMapMaxD[2] = to->gainMapMaxD[1] = to->gainMapMaxD[0]; + bool isSingleChannel = from->are_all_channels_identical(); + for (int i = 0; i < (isSingleChannel ? 1 : 3); i++) { + CONVERT_FLT_TO_SIGNED_FRACTION(log2(from->max_content_boost[i]), &to->gainMapMaxN[i], + &to->gainMapMaxD[i]) - CONVERT_FLT_TO_SIGNED_FRACTION(log2(from->min_content_boost), &to->gainMapMinN[0], - &to->gainMapMinD[0]); - to->gainMapMinN[2] = to->gainMapMinN[1] = to->gainMapMinN[0]; - to->gainMapMinD[2] = to->gainMapMinD[1] = to->gainMapMinD[0]; + CONVERT_FLT_TO_SIGNED_FRACTION(log2(from->min_content_boost[i]), &to->gainMapMinN[i], + &to->gainMapMinD[i]); - CONVERT_FLT_TO_UNSIGNED_FRACTION(from->gamma, &to->gainMapGammaN[0], &to->gainMapGammaD[0]); - to->gainMapGammaN[2] = to->gainMapGammaN[1] = to->gainMapGammaN[0]; - to->gainMapGammaD[2] = to->gainMapGammaD[1] = to->gainMapGammaD[0]; + CONVERT_FLT_TO_UNSIGNED_FRACTION(from->gamma[i], &to->gainMapGammaN[i], &to->gainMapGammaD[i]); - CONVERT_FLT_TO_SIGNED_FRACTION(from->offset_sdr, &to->baseOffsetN[0], &to->baseOffsetD[0]); - to->baseOffsetN[2] = to->baseOffsetN[1] = to->baseOffsetN[0]; - to->baseOffsetD[2] = to->baseOffsetD[1] = to->baseOffsetD[0]; + CONVERT_FLT_TO_SIGNED_FRACTION(from->offset_sdr[i], &to->baseOffsetN[i], &to->baseOffsetD[i]); - CONVERT_FLT_TO_SIGNED_FRACTION(from->offset_hdr, &to->alternateOffsetN[0], - &to->alternateOffsetD[0]); - to->alternateOffsetN[2] = to->alternateOffsetN[1] = to->alternateOffsetN[0]; - to->alternateOffsetD[2] = to->alternateOffsetD[1] = to->alternateOffsetD[0]; + CONVERT_FLT_TO_SIGNED_FRACTION(from->offset_hdr[i], &to->alternateOffsetN[i], + &to->alternateOffsetD[i]); + } + + if (isSingleChannel) { + to->gainMapMaxN[2] = to->gainMapMaxN[1] = to->gainMapMaxN[0]; + to->gainMapMaxD[2] = to->gainMapMaxD[1] = to->gainMapMaxD[0]; + + to->gainMapMinN[2] = to->gainMapMinN[1] = to->gainMapMinN[0]; + to->gainMapMinD[2] = to->gainMapMinD[1] = to->gainMapMinD[0]; + + to->gainMapGammaN[2] = to->gainMapGammaN[1] = to->gainMapGammaN[0]; + to->gainMapGammaD[2] = to->gainMapGammaD[1] = to->gainMapGammaD[0]; + + to->baseOffsetN[2] = to->baseOffsetN[1] = to->baseOffsetN[0]; + to->baseOffsetD[2] = to->baseOffsetD[1] = to->baseOffsetD[0]; + + to->alternateOffsetN[2] = to->alternateOffsetN[1] = to->alternateOffsetN[0]; + to->alternateOffsetD[2] = to->alternateOffsetD[1] = to->alternateOffsetD[0]; + } CONVERT_FLT_TO_UNSIGNED_FRACTION(log2(from->hdr_capacity_min), &to->baseHdrHeadroomN, &to->baseHdrHeadroomD); diff --git a/lib/src/gpu/applygainmap_gl.cpp b/lib/src/gpu/applygainmap_gl.cpp index 6454497..f5eba87 100644 --- a/lib/src/gpu/applygainmap_gl.cpp +++ b/lib/src/gpu/applygainmap_gl.cpp @@ -132,25 +132,25 @@ static const std::string getGainMapSampleMultiChannel = R"__SHADER__( )__SHADER__"; static const std::string applyGainMapShader = R"__SHADER__( - uniform float gamma; - uniform float logMinBoost; - uniform float logMaxBoost; + uniform float gamma[3]; + uniform float logMinBoost[3]; + uniform float logMaxBoost[3]; uniform float weight; - uniform float offsetSdr; - uniform float offsetHdr; + uniform float offsetSdr[3]; + uniform float offsetHdr[3]; uniform float normalize; - float applyGainMapSample(const float channel, float gain) { - gain = pow(gain, 1.0f / gamma); - float logBoost = logMinBoost * (1.0f - gain) + logMaxBoost * gain; + float applyGainMapSample(const float channel, float gain, int idx) { + gain = pow(gain, 1.0f / gamma[idx]); + float logBoost = logMinBoost[idx] * (1.0f - gain) + logMaxBoost[idx] * gain; logBoost = exp2(logBoost * weight); - return ((channel + offsetSdr) * logBoost - offsetHdr) / normalize; + return ((channel + offsetSdr[idx]) * logBoost - offsetHdr[idx]) / normalize; } vec3 applyGain(const vec3 color, const vec3 gain) { - return vec3(applyGainMapSample(color.r, gain.r), - applyGainMapSample(color.g, gain.g), - applyGainMapSample(color.b, gain.b)); + return vec3(applyGainMapSample(color.r, gain.r, 0), + applyGainMapSample(color.g, gain.g, 1), + applyGainMapSample(color.b, gain.b, 2)); } )__SHADER__"; @@ -394,11 +394,17 @@ uhdr_error_info_t applyGainMapGLES(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_ glUniform1i(pWidthLocation, sdr_intent->w); glUniform1i(pHeightLocation, sdr_intent->h); - glUniform1f(gammaLocation, gainmap_metadata->gamma); - glUniform1f(logMinBoostLocation, log2(gainmap_metadata->min_content_boost)); - glUniform1f(logMaxBoostLocation, log2(gainmap_metadata->max_content_boost)); - glUniform1f(offsetSdrLocation, gainmap_metadata->offset_sdr); - glUniform1f(offsetHdrLocation, gainmap_metadata->offset_hdr); + glUniform1fv(gammaLocation, 3, gainmap_metadata->gamma); + float logMinBoostValues[3] = {static_cast(log2(gainmap_metadata->min_content_boost[0])), + static_cast(log2(gainmap_metadata->min_content_boost[1])), + static_cast(log2(gainmap_metadata->min_content_boost[2]))}; + float logMaxBoostValues[3] = {static_cast(log2(gainmap_metadata->max_content_boost[0])), + static_cast(log2(gainmap_metadata->max_content_boost[1])), + static_cast(log2(gainmap_metadata->max_content_boost[2]))}; + glUniform1fv(logMinBoostLocation, 3, logMinBoostValues); + glUniform1fv(logMaxBoostLocation, 3, logMaxBoostValues); + glUniform1fv(offsetSdrLocation, 3, gainmap_metadata->offset_sdr); + glUniform1fv(offsetHdrLocation, 3, gainmap_metadata->offset_hdr); float gainmap_weight; if (display_boost != gainmap_metadata->hdr_capacity_max) { gainmap_weight = diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index fda707b..4a22214 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -722,20 +722,20 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ sdrGamutConversionFn, 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; + std::fill_n(gainmap_metadata->max_content_boost, 3, hdr_white_nits / kSdrWhiteNits); + std::fill_n(gainmap_metadata->min_content_boost, 3, 1.0f); + std::fill_n(gainmap_metadata->gamma, 3, mGamma); + std::fill_n(gainmap_metadata->offset_sdr, 3, 0.0f); + std::fill_n(gainmap_metadata->offset_hdr, 3, 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; + gainmap_metadata->hdr_capacity_max = gainmap_metadata->max_content_boost[0]; } - float log2MinBoost = log2(gainmap_metadata->min_content_boost); - float log2MaxBoost = log2(gainmap_metadata->max_content_boost); + float log2MinBoost = log2(gainmap_metadata->min_content_boost[0]); + float log2MaxBoost = log2(gainmap_metadata->max_content_boost[0]); const int threads = (std::min)(GetCPUCoreCount(), 4u); const int jobSizeInRows = 1; @@ -791,13 +791,13 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ size_t pixel_idx = (x + y * dest->stride[UHDR_PLANE_PACKED]) * 3; reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = encodeGain( - sdr_rgb_nits.r, hdr_rgb_nits.r, gainmap_metadata, log2MinBoost, log2MaxBoost); + sdr_rgb_nits.r, hdr_rgb_nits.r, gainmap_metadata, log2MinBoost, log2MaxBoost, 0); reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 1] = encodeGain(sdr_rgb_nits.g, hdr_rgb_nits.g, gainmap_metadata, log2MinBoost, - log2MaxBoost); + log2MaxBoost, 1); reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 2] = encodeGain(sdr_rgb_nits.b, hdr_rgb_nits.b, gainmap_metadata, log2MinBoost, - log2MaxBoost); + log2MaxBoost, 2); } else { float sdr_y_nits; float hdr_y_nits; @@ -811,8 +811,8 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_Y]; - reinterpret_cast(dest->planes[UHDR_PLANE_Y])[pixel_idx] = - encodeGain(sdr_y_nits, hdr_y_nits, gainmap_metadata, log2MinBoost, log2MaxBoost); + reinterpret_cast(dest->planes[UHDR_PLANE_Y])[pixel_idx] = encodeGain( + sdr_y_nits, hdr_y_nits, gainmap_metadata, log2MinBoost, log2MaxBoost, 0); } } } @@ -954,31 +954,40 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ 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); - } - // gain coefficient range [-14.3, 15.6] is capable of representing hdr pels from sdr pels. - // Allowing further excursion might not offer any benefit and on the downside can cause bigger - // error during affine map and inverse affine map. - min_content_boost_log2 = (std::clamp)(min_content_boost_log2, -14.3f, 15.6f); - max_content_boost_log2 = (std::clamp)(max_content_boost_log2, -14.3f, 15.6f); - 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); + // xmp metadata current implementation does not support writing multichannel metadata + // so merge them in to one + if (kWriteXmpMetadata) { + 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); + } + std::fill_n(gainmap_min, 3, min_content_boost_log2); + std::fill_n(gainmap_max, 3, max_content_boost_log2); } - if (fabs(max_content_boost_log2 - min_content_boost_log2) < FLT_EPSILON) { - max_content_boost_log2 += 0.1f; // to avoid div by zero during affine transform + + for (int index = 0; index < (mUseMultiChannelGainMap ? 3 : 1); index++) { + // gain coefficient range [-14.3, 15.6] is capable of representing hdr pels from sdr pels. + // Allowing further excursion might not offer any benefit and on the downside can cause bigger + // error during affine map and inverse affine map. + gainmap_min[index] = (std::clamp)(gainmap_min[index], -14.3f, 15.6f); + gainmap_max[index] = (std::clamp)(gainmap_max[index], -14.3f, 15.6f); + if (this->mMaxContentBoost != FLT_MAX) { + float suggestion = log2(this->mMaxContentBoost); + gainmap_max[index] = (std::min)(gainmap_max[index], suggestion); + } + if (this->mMinContentBoost != FLT_MIN) { + float suggestion = log2(this->mMinContentBoost); + gainmap_min[index] = (std::max)(gainmap_min[index], suggestion); + } + if (fabs(gainmap_max[index] - gainmap_min[index]) < FLT_EPSILON) { + gainmap_max[index] += 0.1f; // to avoid div by zero during affine transform + } } - std::function encodeMap = [this, gainmap_data, map_width, dest, min_content_boost_log2, - max_content_boost_log2, &jobQueue]() -> void { + std::function encodeMap = [this, gainmap_data, map_width, dest, gainmap_min, + gainmap_max, &jobQueue]() -> void { unsigned int rowStart, rowEnd; while (jobQueue.dequeueJob(rowStart, rowEnd)) { @@ -988,8 +997,8 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ size_t src_pixel_idx = j * map_width * 3; for (size_t i = 0; i < map_width * 3; i++) { reinterpret_cast(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); + affineMapGain(gainmap_data[src_pixel_idx + i], gainmap_min[i % 3], + gainmap_max[i % 3], this->mGamma); } } } else { @@ -998,8 +1007,8 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ size_t src_pixel_idx = j * map_width; for (size_t i = 0; i < map_width; i++) { reinterpret_cast(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); + affineMapGain(gainmap_data[src_pixel_idx + i], gainmap_min[0], gainmap_max[0], + this->mGamma); } } } @@ -1020,11 +1029,18 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ 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 = kSdrOffset; - gainmap_metadata->offset_hdr = kHdrOffset; + if (mUseMultiChannelGainMap) { + for (int i = 0; i < 3; i++) { + gainmap_metadata->max_content_boost[i] = exp2(gainmap_max[i]); + gainmap_metadata->min_content_boost[i] = exp2(gainmap_min[i]); + } + } else { + std::fill_n(gainmap_metadata->max_content_boost, 3, exp2(gainmap_max[0])); + std::fill_n(gainmap_metadata->min_content_boost, 3, exp2(gainmap_min[0])); + } + std::fill_n(gainmap_metadata->gamma, 3, this->mGamma); + std::fill_n(gainmap_metadata->offset_sdr, 3, kSdrOffset); + std::fill_n(gainmap_metadata->offset_hdr, 3, kHdrOffset); gainmap_metadata->hdr_capacity_min = 1.0f; if (this->mTargetDispPeakBrightness != -1.0f) { gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits; @@ -1102,6 +1118,15 @@ uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compr return status; } + if (kWriteXmpMetadata && !metadata->are_all_channels_identical()) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "signalling multichannel gainmap metadata in xmp mode is not supported"); + return status; + } + 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 @@ -1398,11 +1423,15 @@ uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_im static_cast(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; + std::copy(uhdr_metadata.min_content_boost, uhdr_metadata.min_content_boost + 3, + gainmap_metadata->min_content_boost); + std::copy(uhdr_metadata.max_content_boost, uhdr_metadata.max_content_boost + 3, + gainmap_metadata->max_content_boost); + std::copy(uhdr_metadata.gamma, uhdr_metadata.gamma + 3, gainmap_metadata->gamma); + std::copy(uhdr_metadata.offset_sdr, uhdr_metadata.offset_sdr + 3, + gainmap_metadata->offset_sdr); + std::copy(uhdr_metadata.offset_hdr, uhdr_metadata.offset_hdr + 3, + gainmap_metadata->offset_hdr); gainmap_metadata->hdr_capacity_min = uhdr_metadata.hdr_capacity_min; gainmap_metadata->hdr_capacity_max = uhdr_metadata.hdr_capacity_max; gainmap_metadata->use_base_cg = uhdr_metadata.use_base_cg; @@ -2560,11 +2589,11 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, uhdr_gainmap_metadata_ext_t meta(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; + std::fill_n(meta.gamma, 3, metadata->gamma); + std::fill_n(meta.offset_sdr, 3, metadata->offsetSdr); + std::fill_n(meta.offset_hdr, 3, metadata->offsetHdr); + std::fill_n(meta.max_content_boost, 3, metadata->maxContentBoost); + std::fill_n(meta.min_content_boost, 3, metadata->minContentBoost); meta.use_base_cg = true; auto result = encodeJPEGR(&input, &gainmap, &meta, &output); @@ -2719,14 +2748,15 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p gainmap_image_ptr->chroma_data = nullptr; } if (metadata) { + if (!meta.are_all_channels_identical()) return ERROR_JPEGR_METADATA_ERROR; 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; + metadata->gamma = meta.gamma[0]; + metadata->offsetSdr = meta.offset_sdr[0]; + metadata->offsetHdr = meta.offset_hdr[0]; + metadata->maxContentBoost = meta.max_content_boost[0]; + metadata->minContentBoost = meta.min_content_boost[0]; } } diff --git a/lib/src/jpegrutils.cpp b/lib/src/jpegrutils.cpp index 079be01..4a00590 100644 --- a/lib/src/jpegrutils.cpp +++ b/lib/src/jpegrutils.cpp @@ -532,7 +532,7 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, kMapVersion.c_str()); return status; } - if (!handler.getMaxContentBoost(&metadata->max_content_boost, &present) || !present) { + if (!handler.getMaxContentBoost(&metadata->max_content_boost[0], &present) || !present) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_ERROR; status.has_detail = 1; @@ -548,7 +548,7 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, kMapHDRCapacityMax.c_str()); return status; } - if (!handler.getMinContentBoost(&metadata->min_content_boost, &present)) { + if (!handler.getMinContentBoost(&metadata->min_content_boost[0], &present)) { if (present) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_ERROR; @@ -557,9 +557,9 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, kMapGainMapMin.c_str()); return status; } - metadata->min_content_boost = 1.0f; + metadata->min_content_boost[0] = 1.0f; } - if (!handler.getGamma(&metadata->gamma, &present)) { + if (!handler.getGamma(&metadata->gamma[0], &present)) { if (present) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_ERROR; @@ -568,9 +568,9 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, kMapGamma.c_str()); return status; } - metadata->gamma = 1.0f; + metadata->gamma[0] = 1.0f; } - if (!handler.getOffsetSdr(&metadata->offset_sdr, &present)) { + if (!handler.getOffsetSdr(&metadata->offset_sdr[0], &present)) { if (present) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_ERROR; @@ -579,9 +579,9 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, kMapOffsetSdr.c_str()); return status; } - metadata->offset_sdr = 1.0f / 64.0f; + metadata->offset_sdr[0] = 1.0f / 64.0f; } - if (!handler.getOffsetHdr(&metadata->offset_hdr, &present)) { + if (!handler.getOffsetHdr(&metadata->offset_hdr[0], &present)) { if (present) { uhdr_error_info_t status; status.error_code = UHDR_CODEC_ERROR; @@ -590,7 +590,7 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, kMapOffsetHdr.c_str()); return status; } - metadata->offset_hdr = 1.0f / 64.0f; + metadata->offset_hdr[0] = 1.0f / 64.0f; } if (!handler.getHdrCapacityMin(&metadata->hdr_capacity_min, &present)) { if (present) { @@ -624,6 +624,11 @@ uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, return status; } metadata->use_base_cg = true; + std::fill_n(metadata->min_content_boost + 1, 2, metadata->min_content_boost[0]); + std::fill_n(metadata->max_content_boost + 1, 2, metadata->max_content_boost[0]); + std::fill_n(metadata->gamma + 1, 2, metadata->gamma[0]); + std::fill_n(metadata->offset_hdr + 1, 2, metadata->offset_hdr[0]); + std::fill_n(metadata->offset_sdr + 1, 2, metadata->offset_sdr[0]); return g_no_error; } @@ -680,11 +685,11 @@ string generateXmpForSecondaryImage(uhdr_gainmap_metadata_ext_t& metadata) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kGainMapPrefix, kGainMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.min_content_boost)); - writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.max_content_boost)); - writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma); - writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offset_sdr); - writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offset_hdr); + writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.min_content_boost[0])); + writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.max_content_boost[0])); + writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma[0]); + writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offset_sdr[0]); + writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offset_hdr[0]); writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdr_capacity_min)); writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdr_capacity_max)); writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp index 1d3f954..f9d1182 100644 --- a/lib/src/ultrahdr_api.cpp +++ b/lib/src/ultrahdr_api.cpp @@ -428,58 +428,68 @@ uhdr_error_info_t uhdr_validate_gainmap_metadata_descriptor(uhdr_gainmap_metadat 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); + } else { + for (int i = 0; i < 3; i++) { + if (!std::isfinite(metadata->min_content_boost[i]) || + !std::isfinite(metadata->max_content_boost[i]) || + !std::isfinite(metadata->offset_sdr[i]) || !std::isfinite(metadata->offset_hdr[i]) || + !std::isfinite(metadata->hdr_capacity_min) || + !std::isfinite(metadata->hdr_capacity_max) || !std::isfinite(metadata->gamma[i])) { + 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[i], metadata->max_content_boost[i], metadata->offset_sdr[i], + metadata->offset_hdr[i], metadata->hdr_capacity_min, metadata->hdr_capacity_max, + metadata->gamma[i]); + } else if (metadata->max_content_boost[i] < metadata->min_content_boost[i]) { + 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[i], metadata->min_content_boost[i]); + } else if (metadata->min_content_boost[i] <= 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[i]); + return status; + } else if (metadata->gamma[i] <= 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[i]); + } else if (metadata->offset_sdr[i] < 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[i]); + } else if (metadata->offset_hdr[i] < 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[i]); + } 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; } @@ -1572,11 +1582,13 @@ uhdr_error_info_t uhdr_dec_probe(uhdr_codec_private_t* dec) { 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; + std::copy(metadata.max_content_boost, metadata.max_content_boost + 3, + handle->m_metadata.max_content_boost); + std::copy(metadata.min_content_boost, metadata.min_content_boost + 3, + handle->m_metadata.min_content_boost); + std::copy(metadata.gamma, metadata.gamma + 3, handle->m_metadata.gamma); + std::copy(metadata.offset_sdr, metadata.offset_sdr + 3, handle->m_metadata.offset_sdr); + std::copy(metadata.offset_hdr, metadata.offset_hdr + 3, handle->m_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_metadata.use_base_cg = metadata.use_base_cg; diff --git a/tests/gainmapmath_test.cpp b/tests/gainmapmath_test.cpp index 7c62c02..82d098f 100644 --- a/tests/gainmapmath_test.cpp +++ b/tests/gainmapmath_test.cpp @@ -1129,11 +1129,11 @@ TEST_F(GainMapMathTest, applyGainLUT) { for (float boost = 1.5; boost <= 12; boost++) { uhdr_gainmap_metadata_ext_t metadata; - metadata.min_content_boost = 1.0f / boost; - metadata.max_content_boost = boost; - metadata.gamma = 1.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.min_content_boost[0] = 1.0f / boost; + metadata.max_content_boost[0] = boost; + metadata.gamma[0] = 1.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; GainLUT gainLUT(&metadata); float weight = (log2(boost) - log2(metadata.hdr_capacity_min)) / (log2(metadata.hdr_capacity_max) - log2(metadata.hdr_capacity_min)); @@ -1167,11 +1167,11 @@ TEST_F(GainMapMathTest, applyGainLUT) { for (float boost = 1.5; boost <= 12; boost++) { uhdr_gainmap_metadata_ext_t metadata; - metadata.min_content_boost = 1.0f; - metadata.max_content_boost = boost; - metadata.gamma = 1.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.min_content_boost[0] = 1.0f; + metadata.max_content_boost[0] = boost; + metadata.gamma[0] = 1.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; GainLUT gainLUT(&metadata); float weight = (log2(boost) - log2(metadata.hdr_capacity_min)) / (log2(metadata.hdr_capacity_max) - log2(metadata.hdr_capacity_min)); @@ -1205,11 +1205,11 @@ TEST_F(GainMapMathTest, applyGainLUT) { for (float boost = 1.5; boost <= 12; boost++) { uhdr_gainmap_metadata_ext_t metadata; - metadata.min_content_boost = 1.0f / powf(boost, 1.0f / 3.0f); - metadata.max_content_boost = boost; - metadata.gamma = 1.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.min_content_boost[0] = 1.0f / powf(boost, 1.0f / 3.0f); + metadata.max_content_boost[0] = boost; + metadata.gamma[0] = 1.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; GainLUT gainLUT(&metadata); float weight = (log2(boost) - log2(metadata.hdr_capacity_min)) / (log2(metadata.hdr_capacity_max) - log2(metadata.hdr_capacity_min)); @@ -1330,13 +1330,13 @@ TEST_F(GainMapMathTest, EncodeGain) { TEST_F(GainMapMathTest, ApplyGain) { uhdr_gainmap_metadata_ext_t metadata; - metadata.min_content_boost = 1.0f / 4.0f; - metadata.max_content_boost = 4.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; - metadata.offset_sdr = 0.0f; - metadata.offset_hdr = 0.0f; - metadata.gamma = 1.0f; + metadata.min_content_boost[0] = 1.0f / 4.0f; + metadata.max_content_boost[0] = 4.0f; + metadata.offset_sdr[0] = 0.0f; + metadata.offset_hdr[0] = 0.0f; + metadata.gamma[0] = 1.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack()); @@ -1348,10 +1348,10 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); - metadata.max_content_boost = 2.0f; - metadata.min_content_boost = 1.0f / 2.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.max_content_boost[0] = 2.0f; + metadata.min_content_boost[0] = 1.0f / 2.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); @@ -1359,10 +1359,10 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); - metadata.max_content_boost = 8.0f; - metadata.min_content_boost = 1.0f / 8.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.max_content_boost[0] = 8.0f; + metadata.min_content_boost[0] = 1.0f / 8.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); @@ -1370,20 +1370,20 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - metadata.max_content_boost = 8.0f; - metadata.min_content_boost = 1.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.max_content_boost[0] = 8.0f; + metadata.min_content_boost[0] = 1.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - metadata.max_content_boost = 8.0f; - metadata.min_content_boost = 0.5f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.max_content_boost[0] = 8.0f; + metadata.min_content_boost[0] = 0.5f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite()); @@ -1392,10 +1392,10 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); Color e = {{{0.0f, 0.5f, 1.0f}}}; - metadata.max_content_boost = 4.0f; - metadata.min_content_boost = 1.0f / 4.0f; - metadata.hdr_capacity_max = metadata.max_content_boost; - metadata.hdr_capacity_min = metadata.min_content_boost; + metadata.max_content_boost[0] = 4.0f; + metadata.min_content_boost[0] = 1.0f / 4.0f; + metadata.hdr_capacity_max = metadata.max_content_boost[0]; + metadata.hdr_capacity_min = metadata.min_content_boost[0]; EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f); EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f); @@ -1625,11 +1625,11 @@ TEST_F(GainMapMathTest, GenerateMapLuminancePq) { TEST_F(GainMapMathTest, ApplyMap) { uhdr_gainmap_metadata_ext_t metadata; - metadata.min_content_boost = 1.0f / 8.0f; - metadata.max_content_boost = 8.0f; - metadata.offset_sdr = 0.0f; - metadata.offset_hdr = 0.0f; - metadata.gamma = 1.0f; + metadata.min_content_boost[0] = 1.0f / 8.0f; + metadata.max_content_boost[0] = 8.0f; + metadata.offset_sdr[0] = 0.0f; + metadata.offset_hdr[0] = 0.0f; + metadata.gamma[0] = 1.0f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), RgbBlack()); @@ -1661,16 +1661,16 @@ TEST_F(GainMapMathTest, ApplyMap) { EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), RgbGreen() / 8.0f); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), RgbBlue() / 8.0f); - metadata.max_content_boost = 8.0f; - metadata.min_content_boost = 1.0f; + metadata.max_content_boost[0] = 8.0f; + metadata.min_content_boost[0] = 1.0f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite()); - metadata.max_content_boost = 8.0f; - metadata.min_content_boost = 0.5f; + metadata.max_content_boost[0] = 8.0f; + metadata.min_content_boost[0] = 0.5f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), RgbWhite() * 4.0f); diff --git a/tests/gainmapmetadata_test.cpp b/tests/gainmapmetadata_test.cpp index 400a7be..f80e052 100644 --- a/tests/gainmapmetadata_test.cpp +++ b/tests/gainmapmetadata_test.cpp @@ -43,11 +43,13 @@ const std::string kIso = "urn:iso:std:iso:ts:21496:-1"; TEST_F(GainMapMetadataTest, encodeMetadataThenDecode) { uhdr_gainmap_metadata_ext_t expected("1.0"); - expected.max_content_boost = 100.5f; - expected.min_content_boost = 1.5f; - expected.gamma = 1.0f; - expected.offset_sdr = 0.0625f; - expected.offset_hdr = 0.0625f; + for (int i = 0; i < 3; i++) { + expected.max_content_boost[i] = 100.5f + i; + expected.min_content_boost[i] = 1.5f + i * 0.1f; + expected.gamma[i] = 1.0f + i * 0.01f; + expected.offset_sdr[i] = 0.0625f + i * 0.025f; + expected.offset_hdr[i] = 0.0625f + i * 0.025f; + } expected.hdr_capacity_min = 1.0f; expected.hdr_capacity_max = 10000.0f / 203.0f; expected.use_base_cg = false; @@ -72,19 +74,23 @@ TEST_F(GainMapMetadataTest, encodeMetadataThenDecode) { .error_code, UHDR_CODEC_OK); - EXPECT_FLOAT_EQ(expected.max_content_boost, decodedUHdrMetadata.max_content_boost); - EXPECT_FLOAT_EQ(expected.min_content_boost, decodedUHdrMetadata.min_content_boost); - EXPECT_FLOAT_EQ(expected.gamma, decodedUHdrMetadata.gamma); - EXPECT_FLOAT_EQ(expected.offset_sdr, decodedUHdrMetadata.offset_sdr); - EXPECT_FLOAT_EQ(expected.offset_hdr, decodedUHdrMetadata.offset_hdr); + for (int i = 0; i < 3; i++) { + EXPECT_FLOAT_EQ(expected.max_content_boost[i], decodedUHdrMetadata.max_content_boost[i]); + EXPECT_FLOAT_EQ(expected.min_content_boost[i], decodedUHdrMetadata.min_content_boost[i]); + EXPECT_FLOAT_EQ(expected.gamma[i], decodedUHdrMetadata.gamma[i]); + EXPECT_FLOAT_EQ(expected.offset_sdr[i], decodedUHdrMetadata.offset_sdr[i]); + EXPECT_FLOAT_EQ(expected.offset_hdr[i], decodedUHdrMetadata.offset_hdr[i]); + } EXPECT_FLOAT_EQ(expected.hdr_capacity_min, decodedUHdrMetadata.hdr_capacity_min); EXPECT_FLOAT_EQ(expected.hdr_capacity_max, decodedUHdrMetadata.hdr_capacity_max); EXPECT_EQ(expected.use_base_cg, decodedUHdrMetadata.use_base_cg); data.clear(); - expected.min_content_boost = 0.000578369f; - expected.offset_sdr = -0.0625f; - expected.offset_hdr = -0.0625f; + for (int i = 0; i < 3; i++) { + expected.min_content_boost[i] = 0.000578369f + i * 0.001f; + expected.offset_sdr[i] = -0.0625f + i * 0.001f; + expected.offset_hdr[i] = -0.0625f + i * 0.001f; + } expected.hdr_capacity_max = 1000.0f / 203.0f; expected.use_base_cg = true; @@ -100,11 +106,13 @@ TEST_F(GainMapMetadataTest, encodeMetadataThenDecode) { .error_code, UHDR_CODEC_OK); - EXPECT_FLOAT_EQ(expected.max_content_boost, decodedUHdrMetadata.max_content_boost); - EXPECT_FLOAT_EQ(expected.min_content_boost, decodedUHdrMetadata.min_content_boost); - EXPECT_FLOAT_EQ(expected.gamma, decodedUHdrMetadata.gamma); - EXPECT_FLOAT_EQ(expected.offset_sdr, decodedUHdrMetadata.offset_sdr); - EXPECT_FLOAT_EQ(expected.offset_hdr, decodedUHdrMetadata.offset_hdr); + for (int i = 0; i < 3; i++) { + EXPECT_FLOAT_EQ(expected.max_content_boost[i], decodedUHdrMetadata.max_content_boost[i]); + EXPECT_FLOAT_EQ(expected.min_content_boost[i], decodedUHdrMetadata.min_content_boost[i]); + EXPECT_FLOAT_EQ(expected.gamma[i], decodedUHdrMetadata.gamma[i]); + EXPECT_FLOAT_EQ(expected.offset_sdr[i], decodedUHdrMetadata.offset_sdr[i]); + EXPECT_FLOAT_EQ(expected.offset_hdr[i], decodedUHdrMetadata.offset_hdr[i]); + } EXPECT_FLOAT_EQ(expected.hdr_capacity_min, decodedUHdrMetadata.hdr_capacity_min); EXPECT_FLOAT_EQ(expected.hdr_capacity_max, decodedUHdrMetadata.hdr_capacity_max); EXPECT_EQ(expected.use_base_cg, decodedUHdrMetadata.use_base_cg); diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp index 868b6f3..2cf5265 100644 --- a/tests/jpegr_test.cpp +++ b/tests/jpegr_test.cpp @@ -1401,16 +1401,16 @@ TEST(JpegRTest, DecodeAPIWithInvalidArgs) { } TEST(JpegRTest, writeXmpThenRead) { - uhdr_gainmap_metadata_ext_t metadata_expected; - metadata_expected.version = "1.0"; - metadata_expected.max_content_boost = 1.25f; - metadata_expected.min_content_boost = 0.75f; - metadata_expected.gamma = 1.0f; - metadata_expected.offset_sdr = 0.0f; - metadata_expected.offset_hdr = 0.0f; + uhdr_gainmap_metadata_ext_t metadata_expected("1.0"); + std::fill_n(metadata_expected.max_content_boost, 3, 1.25f); + std::fill_n(metadata_expected.min_content_boost, 3, 0.75f); + std::fill_n(metadata_expected.gamma, 3, 1.0f); + std::fill_n(metadata_expected.offset_sdr, 3, 0.0f); + std::fill_n(metadata_expected.offset_hdr, 3, 0.0f); metadata_expected.hdr_capacity_min = 1.0f; - metadata_expected.hdr_capacity_max = metadata_expected.max_content_boost; + metadata_expected.hdr_capacity_max = metadata_expected.max_content_boost[0]; metadata_expected.use_base_cg = true; + const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const size_t nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator @@ -1426,11 +1426,11 @@ TEST(JpegRTest, writeXmpThenRead) { uhdr_gainmap_metadata_ext_t metadata_read; EXPECT_EQ(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read).error_code, UHDR_CODEC_OK); - EXPECT_FLOAT_EQ(metadata_expected.max_content_boost, metadata_read.max_content_boost); - EXPECT_FLOAT_EQ(metadata_expected.min_content_boost, metadata_read.min_content_boost); - EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma); - EXPECT_FLOAT_EQ(metadata_expected.offset_sdr, metadata_read.offset_sdr); - EXPECT_FLOAT_EQ(metadata_expected.offset_hdr, metadata_read.offset_hdr); + EXPECT_FLOAT_EQ(metadata_expected.max_content_boost[0], metadata_read.max_content_boost[0]); + EXPECT_FLOAT_EQ(metadata_expected.min_content_boost[0], metadata_read.min_content_boost[0]); + EXPECT_FLOAT_EQ(metadata_expected.gamma[0], metadata_read.gamma[0]); + EXPECT_FLOAT_EQ(metadata_expected.offset_sdr[0], metadata_read.offset_sdr[0]); + EXPECT_FLOAT_EQ(metadata_expected.offset_hdr[0], metadata_read.offset_hdr[0]); EXPECT_FLOAT_EQ(metadata_expected.hdr_capacity_min, metadata_read.hdr_capacity_min); EXPECT_FLOAT_EQ(metadata_expected.hdr_capacity_max, metadata_read.hdr_capacity_max); EXPECT_TRUE(metadata_read.use_base_cg); diff --git a/ultrahdr_api.h b/ultrahdr_api.h index c7a0d80..c73a670 100644 --- a/ultrahdr_api.h +++ b/ultrahdr_api.h @@ -253,21 +253,21 @@ typedef struct uhdr_mem_block { /**\brief Gain map metadata. */ typedef struct uhdr_gainmap_metadata { - float max_content_boost; /**< Value to control how much brighter an image can get, when shown on + float max_content_boost[3]; /**< Value to control how much brighter an image can get, when shown + on an HDR display, relative to the SDR rendition. This is constant for + a given image. Value MUST be in linear scale. */ + float min_content_boost[3]; /**< Value to control how much darker an image can get, when shown on an HDR display, relative to the SDR rendition. This is constant for a given image. Value MUST be in linear scale. */ - float min_content_boost; /**< Value to control how much darker an image can get, when shown on - an HDR display, relative to the SDR rendition. This is constant for a - given image. Value MUST be in linear scale. */ - float gamma; /**< Encoding Gamma of the gainmap image. */ - float offset_sdr; /**< The offset to apply to the SDR pixel values during gainmap generation and - application. */ - float offset_hdr; /**< The offset to apply to the HDR pixel values during gainmap generation and - application. */ - float hdr_capacity_min; /**< Minimum display boost value for which the map is applied completely. - Value MUST be in linear scale. */ - float hdr_capacity_max; /**< Maximum display boost value for which the map is applied completely. - Value MUST be in linear scale. */ + float gamma[3]; /**< Encoding Gamma of the gainmap image. */ + float offset_sdr[3]; /**< The offset to apply to the SDR pixel values during gainmap generation + and application. */ + float offset_hdr[3]; /**< The offset to apply to the HDR pixel values during gainmap generation + and application. */ + float hdr_capacity_min; /**< Minimum display boost value for which the map is applied completely. + Value MUST be in linear scale. */ + float hdr_capacity_max; /**< Maximum display boost value for which the map is applied completely. + Value MUST be in linear scale. */ int use_base_cg; /**< Is gainmap application space same as base image color space */ } uhdr_gainmap_metadata_t; /**< alias for struct uhdr_gainmap_metadata */