From 613fee15db6cfae17eee16c77601bf38d24c7537 Mon Sep 17 00:00:00 2001 From: Ram Mohan M Date: Sun, 20 Oct 2024 19:07:34 +0530 Subject: [PATCH] add support for encoding linear half-float images This changes adds support for encoding linear half float hdr intent inputs. linear color transfer inputs are restricted to half float color format only to keep it symmetrical with decoder. fixes #279 Test: ./ultrahdr_app --- README.md | 8 +-- examples/ultrahdr_app.cpp | 46 +++++++++++- java/UltraHdrApp.java | 32 ++++++++- .../media/codecs/ultrahdr/UltraHDRCommon.java | 5 +- .../codecs/ultrahdr/UltraHDREncoder.java | 43 +++++++++++ ...le_media_codecs_ultrahdr_UltraHDREncoder.h | 8 +++ java/jni/ultrahdr-jni.cpp | 37 ++++++++-- lib/include/ultrahdr/gainmapmath.h | 72 +++++++++++++++++-- lib/include/ultrahdr/jpegr.h | 25 ++++--- lib/src/gainmapmath.cpp | 19 +++++ lib/src/jpegr.cpp | 55 ++++++++------ lib/src/ultrahdr_api.cpp | 25 +++++-- ultrahdr_api.h | 31 ++++---- 13 files changed, 334 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index da5279fa..965fd993 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ libultrahdr includes two classes of APIs, one to compress and the other to decom | Scenario | Hdr intent raw | Sdr intent raw | Sdr intent compressed | Gain map compressed | Quality | Exif | Use Case | |:---------:| :----------: | :----------: | :---------------------: | :-------------------: | :-------: | :---------: | :-------- | -| API - 0 | P010 or rgb1010102 | No | No | No | Optional| Optional | Used if, only hdr raw intent is present. [^1] | -| API - 1 | P010 or rgb1010102 | YUV420 or rgba8888 | No | No | Optional| Optional | Used if, hdr raw and sdr raw intents are present.[^2] | -| API - 2 | P010 or rgb1010102 | YUV420 or rgba8888 | Yes | No | No | No | Used if, hdr raw, sdr raw and sdr compressed intents are present.[^3] | -| API - 3 | P010 or rgb1010102 | No | Yes | No | No | No | Used if, hdr raw and sdr compressed intents are present.[^4] | +| API - 0 | P010 or rgba1010102 or rgbaf16 | No | No | No | Optional| Optional | Used if, only hdr raw intent is present. [^1] | +| API - 1 | P010 or rgba1010102 or rgbaf16 | YUV420 or rgba8888 | No | No | Optional| Optional | Used if, hdr raw and sdr raw intents are present.[^2] | +| API - 2 | P010 or rgba1010102 or rgbaf16 | YUV420 or rgba8888 | Yes | No | No | No | Used if, hdr raw, sdr raw and sdr compressed intents are present.[^3] | +| API - 3 | P010 or rgba1010102 or rgbaf16 | No | Yes | No | No | No | Used if, hdr raw and sdr compressed intents are present.[^4] | | API - 4 | No | No | Yes | Yes | No | No | Used if, sdr compressed, gain map compressed and GainMap Metadata are present.[^5] | [^1]: Tonemap hdr to sdr. Compute gain map from hdr and sdr. Compress sdr and gainmap at quality configured. Add exif if provided. Combine sdr compressed, gainmap in multi picture format with gainmap metadata. diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp index bff0adc8..889a55f7 100644 --- a/examples/ultrahdr_app.cpp +++ b/examples/ultrahdr_app.cpp @@ -181,6 +181,10 @@ static bool loadFile(const char* filename, uhdr_raw_image_t* handle) { const int bpp = 4; READ_BYTES(ifd, handle->planes[UHDR_PLANE_PACKED], handle->w * handle->h * bpp) return true; + } else if (handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { + const int bpp = 8; + READ_BYTES(ifd, handle->planes[UHDR_PLANE_PACKED], handle->w * handle->h * bpp) + return true; } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) { READ_BYTES(ifd, handle->planes[UHDR_PLANE_Y], handle->w * handle->h) READ_BYTES(ifd, handle->planes[UHDR_PLANE_U], (handle->w / 2) * (handle->h / 2)) @@ -332,6 +336,10 @@ class UltraHdrAppInput { free(mRawRgba1010102Image.planes[i]); mRawRgba1010102Image.planes[i] = nullptr; } + if (mRawRgbaF16Image.planes[i]) { + free(mRawRgbaF16Image.planes[i]); + mRawRgbaF16Image.planes[i] = nullptr; + } if (mRawYuv420Image.planes[i]) { free(mRawYuv420Image.planes[i]); mRawYuv420Image.planes[i] = nullptr; @@ -356,6 +364,7 @@ class UltraHdrAppInput { bool fillUhdrImageHandle(); bool fillP010ImageHandle(); bool fillRGBA1010102ImageHandle(); + bool fillRGBAF16ImageHandle(); bool convertP010ToRGBImage(); bool fillYuv420ImageHandle(); bool fillRGBA8888ImageHandle(); @@ -406,6 +415,7 @@ class UltraHdrAppInput { uhdr_raw_image_t mRawP010Image{}; uhdr_raw_image_t mRawRgba1010102Image{}; + uhdr_raw_image_t mRawRgbaF16Image{}; uhdr_raw_image_t mRawYuv420Image{}; uhdr_raw_image_t mRawRgba8888Image{}; uhdr_compressed_image_t mSdrIntentCompressedImage{}; @@ -471,6 +481,23 @@ bool UltraHdrAppInput::fillRGBA1010102ImageHandle() { return loadFile(mHdrIntentRawFile, &mRawRgba1010102Image); } +bool UltraHdrAppInput::fillRGBAF16ImageHandle() { + const int bpp = 8; + mRawRgbaF16Image.fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat; + mRawRgbaF16Image.cg = mHdrCg; + mRawRgbaF16Image.ct = mHdrTf; + mRawRgbaF16Image.range = UHDR_CR_FULL_RANGE; + mRawRgbaF16Image.w = mWidth; + mRawRgbaF16Image.h = mHeight; + mRawRgbaF16Image.planes[UHDR_PLANE_PACKED] = malloc(mWidth * mHeight * bpp); + mRawRgbaF16Image.planes[UHDR_PLANE_UV] = nullptr; + mRawRgbaF16Image.planes[UHDR_PLANE_V] = nullptr; + mRawRgbaF16Image.stride[UHDR_PLANE_PACKED] = mWidth; + mRawRgbaF16Image.stride[UHDR_PLANE_UV] = 0; + mRawRgbaF16Image.stride[UHDR_PLANE_V] = 0; + return loadFile(mHdrIntentRawFile, &mRawRgbaF16Image); +} + bool UltraHdrAppInput::fillRGBA8888ImageHandle() { const int bpp = 4; mRawRgba8888Image.fmt = UHDR_IMG_FMT_32bppRGBA8888; @@ -610,6 +637,11 @@ bool UltraHdrAppInput::encode() { std::cerr << " failed to load file " << mHdrIntentRawFile << std::endl; return false; } + } else if (mHdrCf == UHDR_IMG_FMT_64bppRGBAHalfFloat) { + if (!fillRGBAF16ImageHandle()) { + std::cerr << " failed to load file " << mHdrIntentRawFile << std::endl; + return false; + } } else { std::cerr << " invalid hdr intent color format " << mHdrCf << std::endl; return false; @@ -671,6 +703,8 @@ bool UltraHdrAppInput::encode() { RET_IF_ERR(uhdr_enc_set_raw_image(handle, &mRawP010Image, UHDR_HDR_IMG)) } else if (mHdrCf == UHDR_IMG_FMT_32bppRGBA1010102) { RET_IF_ERR(uhdr_enc_set_raw_image(handle, &mRawRgba1010102Image, UHDR_HDR_IMG)) + } else if (mHdrCf == UHDR_IMG_FMT_64bppRGBAHalfFloat) { + RET_IF_ERR(uhdr_enc_set_raw_image(handle, &mRawRgbaF16Image, UHDR_HDR_IMG)) } } if (mSdrIntentRawFile != nullptr) { @@ -1336,7 +1370,8 @@ static void usage(const char* name) { stderr, " -y raw sdr intent input resource (8-bit), required for encoding scenarios 1, 2. \n"); fprintf(stderr, - " -a raw hdr intent color format, optional. [0:p010, 5:rgba1010102 (default)] \n"); + " -a raw hdr intent color format, optional. [0:p010, 4: rgbahalffloat, " + "5:rgba1010102 (default)] \n"); fprintf(stderr, " -b raw sdr intent color format, optional. [1:yuv420, 3:rgba8888 (default)] \n"); fprintf(stderr, @@ -1353,6 +1388,12 @@ static void usage(const char* name) { " -c sdr intent color gamut, optional. [0:bt709 (default), 1:p3, 2:bt2100] \n"); fprintf(stderr, " -t hdr intent color transfer, optional. [0:linear, 1:hlg (default), 2:pq] \n"); + fprintf(stderr, + " It should be noted that not all combinations of input color format and input " + "color transfer are supported. \n" + " srgb color transfer shall be paired with rgba8888 or yuv420 only. \n" + " hlg, pq shall be paired with rgba1010102 or p010. \n" + " linear shall be paired with rgbahalffloat. \n"); fprintf(stderr, " -q quality factor to be used while encoding sdr intent, optional. [0-100], 95 : " "default.\n"); @@ -1635,7 +1676,8 @@ int main(int argc, char* argv[]) { appInput.convertRgba8888ToYUV444Image(); appInput.computeYUVSdrPSNR(); } - } else if (out_cf == UHDR_IMG_FMT_32bppRGBA1010102 && hdr_intent_raw_file != nullptr) { + } else if (out_cf == UHDR_IMG_FMT_32bppRGBA1010102 && hdr_intent_raw_file != nullptr && + hdr_cf != UHDR_IMG_FMT_64bppRGBAHalfFloat) { if (hdr_cf == UHDR_IMG_FMT_24bppYCbCrP010) { appInput.convertP010ToRGBImage(); } diff --git a/java/UltraHdrApp.java b/java/UltraHdrApp.java index e76c4b69..2b90dab7 100644 --- a/java/UltraHdrApp.java +++ b/java/UltraHdrApp.java @@ -69,6 +69,7 @@ public class UltraHdrApp { byte[] mYuv420YData, mYuv420CbData, mYuv420CrData; short[] mP010YData, mP010CbCrData; int[] mRgba1010102Data, mRgba8888Data; + long[] mRgbaF16Data; byte[] mCompressedImageData; byte[] mGainMapCompressedImageData; byte[] mExifData; @@ -197,6 +198,22 @@ public void fillRGBA1010102ImageHandle() throws IOException { byteBuffer.asIntBuffer().get(mRgba1010102Data); } + public void fillRGBAF16ImageHandle() throws IOException { + final int bpp = 8; + final int rgbSampleCount = mHeight * mWidth; + final int expectedSize = rgbSampleCount * bpp; + byte[] data = readFile(mHdrIntentRawFile); + if (data.length < expectedSize) { + throw new RuntimeException("For the configured width, height, RGBA1010102 Image File is" + + " expected to contain " + expectedSize + " bytes, but the file has " + + data.length + " bytes"); + } + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + byteBuffer.order(ByteOrder.nativeOrder()); + mRgbaF16Data = new long[mHeight * mWidth]; + byteBuffer.asLongBuffer().get(mRgbaF16Data); + } + public void fillRGBA8888Handle() throws IOException { final int bpp = 4; final int rgbSampleCount = mHeight * mWidth; @@ -344,6 +361,10 @@ public void encode() throws Exception { fillRGBA1010102ImageHandle(); handle.setRawImage(mRgba1010102Data, mWidth, mHeight, mWidth, mHdrCg, mHdrTf, UHDR_CR_FULL_RANGE, mHdrCf, UHDR_HDR_IMG); + } else if (mHdrCf == UHDR_IMG_FMT_64bppRGBAHalfFloat) { + fillRGBAF16ImageHandle(); + handle.setRawImage(mRgbaF16Data, mWidth, mHeight, mWidth, mHdrCg, mHdrTf, + UHDR_CR_FULL_RANGE, mHdrCf, UHDR_HDR_IMG); } else { throw new IllegalArgumentException("invalid hdr intent color format " + mHdrCf); } @@ -429,8 +450,8 @@ public static void usage() { + " scenarios 0, 1, 2, 3."); System.out.println(" -y raw sdr intent input resource (8-bit), required for encoding" + " scenarios 1, 2."); - System.out.println(" -a raw hdr intent color format, optional. [0:p010, 5:rgba1010102" - + " (default)]"); + System.out.println(" -a raw hdr intent color format, optional. [0:p010, " + + "4: rgbahalffloat, 5:rgba1010102 (default)]"); System.out.println(" -b raw sdr intent color format, optional. [1:yuv420, 3:rgba8888" + " (default)]"); System.out.println(" -i compressed sdr intent input resource (jpeg), required for " @@ -447,6 +468,13 @@ public static void usage() { " -c sdr intent color gamut, optional. [0:bt709 (default), 1:p3, 2:bt2100]"); System.out.println( " -t hdr intent color transfer, optional. [0:linear, 1:hlg (default), 2:pq]"); + System.out.println( + " It should be noted that not all combinations of input color format and" + + " input color transfer are supported."); + System.out.println( + " srgb color transfer shall be paired with rgba8888 or yuv420 only."); + System.out.println(" hlg, pq shall be paired with rgba1010102 or p010."); + System.out.println(" linear shall be paired with rgbahalffloat."); System.out.println(" -q quality factor to be used while encoding sdr intent, " + "optional. [0-100], 95 : default."); System.out.println(" -R color range of hdr intent, optional. [0:narrow-range " diff --git a/java/com/google/media/codecs/ultrahdr/UltraHDRCommon.java b/java/com/google/media/codecs/ultrahdr/UltraHDRCommon.java index 2dfb7208..ec8d1112 100644 --- a/java/com/google/media/codecs/ultrahdr/UltraHDRCommon.java +++ b/java/com/google/media/codecs/ultrahdr/UltraHDRCommon.java @@ -79,8 +79,9 @@ public class UltraHDRCommon { public static final int UHDR_IMG_FMT_32bppRGBA8888 = 3; /** - * 64 bits per pixel RGBA color format, with 16-bit signed - * floating point red, green, blue, and alpha components. + * 64 bits per pixel, 16 bits per channel, half-precision floating point RGBA color format. + * In a pixel even though each channel has storage space of 16 bits, the nominal range is + * expected to be [0.0..(10000/203)] *

* *

diff --git a/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java b/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java
index 463eada7..bc3427d3 100644
--- a/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java
+++ b/java/com/google/media/codecs/ultrahdr/UltraHDREncoder.java
@@ -20,6 +20,7 @@
 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_24bppYCbCrP010;
 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_32bppRGBA1010102;
 import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_32bppRGBA8888;
+import static com.google.media.codecs.ultrahdr.UltraHDRCommon.UHDR_IMG_FMT_64bppRGBAHalfFloat;
 
 import java.io.IOException;
 
@@ -117,6 +118,44 @@ public void setRawImage(int[] rgbBuff, int width, int height, int rgbStride, int
                 colorFormat, intent);
     }
 
+    /**
+     * Add raw image info to encoder context. This interface is used for adding 64 bits-per-pixel
+     * packed formats. The function goes through all the arguments and checks for their sanity.
+     * If no anomalies are seen then the image info is added to internal list. Repeated calls to
+     * this function will replace the old entry with the current.
+     *
+     * @param rgbBuff       rgb buffer handle
+     * @param width         image width
+     * @param height        image height
+     * @param rgbStride     rgb buffer stride
+     * @param colorGamut    color gamut of input image
+     * @param colorTransfer color transfer of input image
+     * @param colorRange    color range of input image
+     * @param colorFormat   color format of input image
+     * @param intent        {@link UltraHDRCommon#UHDR_HDR_IMG} for hdr intent
+     * @throws IOException If parameters are not valid or current encoder instance is not valid
+     *                     or current encoder instance is not suitable for configuration
+     *                     exception is thrown
+     */
+    public void setRawImage(long[] rgbBuff, int width, int height, int rgbStride, int colorGamut,
+            int colorTransfer, int colorRange, int colorFormat, int intent) throws IOException {
+        if (rgbBuff == null) {
+            throw new IOException("received null for image data handle");
+        }
+        if (width <= 0 || height <= 0) {
+            throw new IOException("received bad width and/or height, width or height is <= 0");
+        }
+        if (rgbStride <= 0) {
+            throw new IOException("received bad stride, stride is <= 0");
+        }
+        if (colorFormat != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+            throw new IOException("received unsupported color format. supported color formats are"
+                    + "{UHDR_IMG_FMT_64bppRGBAHalfFloat}");
+        }
+        setRawImageNative(rgbBuff, width, height, rgbStride, colorGamut, colorTransfer, colorRange,
+                colorFormat, intent);
+    }
+
     /**
      * Add raw image info to encoder context. This interface is used for adding 16 bits-per-sample
      * pixel formats. The function goes through all the arguments and checks for their sanity. If
@@ -476,6 +515,10 @@ private native void setRawImageNative(int[] rgbBuff, int width, int height, int
             int colorGamut, int colorTransfer, int colorRange, int colorFormat, int intent)
             throws IOException;
 
+    private native void setRawImageNative(long[] rgbBuff, int width, int height, int rgbStride,
+            int colorGamut, int colorTransfer, int colorRange, int colorFormat, int intent)
+            throws IOException;
+
     private native void setRawImageNative(short[] yBuff, short[] uvBuff, int width, int height,
             int yStride, int uvStride, int colorGamut, int colorTransfer, int colorRange,
             int colorFormat, int intent) 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 7c27643d..985b6aea 100644
--- a/java/jni/com_google_media_codecs_ultrahdr_UltraHDREncoder.h
+++ b/java/jni/com_google_media_codecs_ultrahdr_UltraHDREncoder.h
@@ -41,6 +41,14 @@ JNIEXPORT void JNICALL Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_des
 JNIEXPORT void JNICALL Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3IIIIIIIII
   (JNIEnv *, jobject, jintArray, jint, jint, jint, jint, jint, jint, jint, jint);
 
+/*
+ * Class:     com_google_media_codecs_ultrahdr_UltraHDREncoder
+ * Method:    setRawImageNative
+ * Signature: ([JIIIIIIII)V
+ */
+JNIEXPORT void JNICALL Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3JIIIIIIII
+  (JNIEnv *, jobject, jlongArray, jint, jint, jint, jint, jint, jint, jint, jint);
+
 /*
  * Class:     com_google_media_codecs_ultrahdr_UltraHDREncoder
  * Method:    setRawImageNative
diff --git a/java/jni/ultrahdr-jni.cpp b/java/jni/ultrahdr-jni.cpp
index 64db9af1..f70a4569 100644
--- a/java/jni/ultrahdr-jni.cpp
+++ b/java/jni/ultrahdr-jni.cpp
@@ -98,7 +98,7 @@ Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3IIIII
   RET_IF_TRUE(handle == 0, "java/io/IOException", "invalid encoder instance")
   jsize length = env->GetArrayLength(rgb_buff);
   RET_IF_TRUE(length < height * rgb_stride, "java/io/IOException",
-              "compressed image luma byteArray size is less than required size")
+              "raw image rgba byteArray size is less than required size")
   jint *rgbBody = env->GetIntArrayElements(rgb_buff, nullptr);
   uhdr_raw_image_t img{(uhdr_img_fmt_t)color_format,
                        (uhdr_color_gamut_t)color_gamut,
@@ -115,6 +115,31 @@ Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3IIIII
               status.has_detail ? status.detail : "uhdr_enc_set_raw_image() returned with error")
 }
 
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3JIIIIIIII(
+    JNIEnv *env, jobject thiz, jlongArray rgb_buff, jint width, jint height, jint rgb_stride,
+    jint color_gamut, jint color_transfer, jint color_range, jint color_format, jint intent) {
+  GET_HANDLE()
+  RET_IF_TRUE(handle == 0, "java/io/IOException", "invalid encoder instance")
+  jsize length = env->GetArrayLength(rgb_buff);
+  RET_IF_TRUE(length < height * rgb_stride, "java/io/IOException",
+              "raw image rgba byteArray size is less than required size")
+  jlong *rgbBody = env->GetLongArrayElements(rgb_buff, nullptr);
+  uhdr_raw_image_t img{(uhdr_img_fmt_t)color_format,
+                       (uhdr_color_gamut_t)color_gamut,
+                       (uhdr_color_transfer_t)color_transfer,
+                       (uhdr_color_range_t)color_range,
+                       (unsigned int)width,
+                       (unsigned int)height,
+                       {rgbBody, nullptr, nullptr},
+                       {(unsigned int)rgb_stride, 0u, 0u}};
+  auto status =
+      uhdr_enc_set_raw_image((uhdr_codec_private_t *)handle, &img, (uhdr_img_label_t)intent);
+  env->ReleaseLongArrayElements(rgb_buff, rgbBody, 0);
+  RET_IF_TRUE(status.error_code != UHDR_CODEC_OK, "java/io/IOException",
+              status.has_detail ? status.detail : "uhdr_enc_set_raw_image() returned with error")
+}
+
 extern "C" JNIEXPORT void JNICALL
 Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3S_3SIIIIIIIII(
     JNIEnv *env, jobject thiz, jshortArray y_buff, jshortArray uv_buff, jint width, jint height,
@@ -124,10 +149,10 @@ Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3S_3SI
   RET_IF_TRUE(handle == 0, "java/io/IOException", "invalid encoder instance")
   jsize length = env->GetArrayLength(y_buff);
   RET_IF_TRUE(length < height * y_stride, "java/io/IOException",
-              "compressed image luma byteArray size is less than required size")
+              "raw image luma byteArray size is less than required size")
   length = env->GetArrayLength(uv_buff);
   RET_IF_TRUE(length < height * uv_stride / 2, "java/io/IOException",
-              "compressed image cb byteArray size is less than required size")
+              "raw image chroma byteArray size is less than required size")
   jshort *lumaBody = env->GetShortArrayElements(y_buff, nullptr);
   jshort *chromaBody = env->GetShortArrayElements(uv_buff, nullptr);
   uhdr_raw_image_t img{(uhdr_img_fmt_t)color_format,
@@ -155,13 +180,13 @@ Java_com_google_media_codecs_ultrahdr_UltraHDREncoder_setRawImageNative___3B_3B_
   RET_IF_TRUE(handle == 0, "java/io/IOException", "invalid encoder instance")
   jsize length = env->GetArrayLength(y_buff);
   RET_IF_TRUE(length < height * y_stride, "java/io/IOException",
-              "compressed image luma byteArray size is less than required size")
+              "raw image luma byteArray size is less than required size")
   length = env->GetArrayLength(u_buff);
   RET_IF_TRUE(length < height * u_stride / 4, "java/io/IOException",
-              "compressed image cb byteArray size is less than required size")
+              "raw image cb byteArray size is less than required size")
   length = env->GetArrayLength(v_buff);
   RET_IF_TRUE(length < height * v_stride / 4, "java/io/IOException",
-              "compressed image cb byteArray size is less than required size")
+              "raw image cb byteArray size is less than required size")
   jbyte *lumaBody = env->GetByteArrayElements(y_buff, nullptr);
   jbyte *cbBody = env->GetByteArrayElements(u_buff, nullptr);
   jbyte *crBody = env->GetByteArrayElements(v_buff, nullptr);
diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h
index e7cd8a6a..edaf645c 100644
--- a/lib/include/ultrahdr/gainmapmath.h
+++ b/lib/include/ultrahdr/gainmapmath.h
@@ -153,8 +153,8 @@ inline Color operator/(const Color& lhs, const float rhs) {
 ////////////////////////////////////////////////////////////////////////////////
 // Float to Half and Half to Float conversions
 union FloatUIntUnion {
-  uint32_t fUInt;
-  float fFloat;
+  uint32_t mUInt;
+  float mFloat;
 };
 
 // FIXME: The shift operations in this function are causing UBSAN (Undefined-shift) errors
@@ -165,9 +165,9 @@ union FloatUIntUnion {
 UHDR_NO_SANITIZE_UNDEFINED
 inline uint16_t floatToHalf(float f) {
   FloatUIntUnion floatUnion;
-  floatUnion.fFloat = f;
+  floatUnion.mFloat = f;
   // round-to-nearest-even: add last bit after truncated mantissa
-  const uint32_t b = floatUnion.fUInt + 0x00001000;
+  const uint32_t b = floatUnion.mUInt + 0x00001000;
 
   const int32_t e = (b & 0x7F800000) >> 23;  // exponent
   const uint32_t m = b & 0x007FFFFF;         // mantissa
@@ -178,6 +178,50 @@ inline uint16_t floatToHalf(float f) {
          (e > 143) * 0x7FFF;
 }
 
+// Taken from frameworks/base/libs/hwui/jni/android_graphics_ColorSpace.cpp
+
+#if defined(__ANDROID__)  // __fp16 is not defined on non-Android builds
+inline float halfToFloat(uint16_t bits) {
+  __fp16 h;
+  memcpy(&h, &bits, 2);
+  return (float)h;
+}
+#else
+// This is Skia's implementation of SkHalfToFloat, which is
+// based on Fabien Giesen's half_to_float_fast2()
+// see https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/
+inline uint16_t halfMantissa(uint16_t h) { return h & 0x03ff; }
+
+inline uint16_t halfExponent(uint16_t h) { return (h >> 10) & 0x001f; }
+
+inline uint16_t halfSign(uint16_t h) { return h >> 15; }
+
+inline float halfToFloat(uint16_t bits) {
+  static const FloatUIntUnion magic = {126 << 23};
+  FloatUIntUnion o;
+
+  if (halfExponent(bits) == 0) {
+    // Zero / Denormal
+    o.mUInt = magic.mUInt + halfMantissa(bits);
+    o.mFloat -= magic.mFloat;
+  } else {
+    // Set mantissa
+    o.mUInt = halfMantissa(bits) << 13;
+    // Set exponent
+    if (halfExponent(bits) == 0x1f) {
+      // Inf/NaN
+      o.mUInt |= (255 << 23);
+    } else {
+      o.mUInt |= ((127 - 15 + halfExponent(bits)) << 23);
+    }
+  }
+
+  // Set sign
+  o.mUInt |= (halfSign(bits) << 31);
+  return o.mFloat;
+}
+#endif  // defined(__ANDROID__)
+
 ////////////////////////////////////////////////////////////////////////////////
 // Use Shepard's method for inverse distance weighting. For more information:
 // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
@@ -329,6 +373,7 @@ Color getP010Pixel(uhdr_raw_image_t* image, size_t x, size_t y);
 Color getYuv444Pixel10bit(uhdr_raw_image_t* image, size_t x, size_t y);
 Color getRgba8888Pixel(uhdr_raw_image_t* image, size_t x, size_t y);
 Color getRgba1010102Pixel(uhdr_raw_image_t* image, size_t x, size_t y);
+Color getRgbaF16Pixel(uhdr_raw_image_t* image, size_t x, size_t y);
 
 // Sample the image at the provided location, with a weighting based on nearby pixels and the map
 // scale factor.
@@ -339,6 +384,7 @@ Color sampleP010(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_
 Color sampleYuv44410bit(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y);
 Color sampleRgba8888(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y);
 Color sampleRgba1010102(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y);
+Color sampleRgbaF16(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y);
 
 // Put pixel in the image at the provided location.
 void putRgba8888Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel);
@@ -498,6 +544,24 @@ static inline Color clampPixelFloat(Color e) {
   return {{{clampPixelFloat(e.r), clampPixelFloat(e.g), clampPixelFloat(e.b)}}};
 }
 
+// maximum limit of pixel value for linear hdr intent raw resource
+static const float kMaxPixelFloatHdrLinear = 10000.0f / 203.0f;
+
+static inline float clampPixelFloatLinear(float value) {
+  return CLIP3(value, 0.0f, kMaxPixelFloatHdrLinear);
+}
+
+static inline Color clampPixelFloatLinear(Color e) {
+  return {{{clampPixelFloatLinear(e.r), clampPixelFloatLinear(e.g), clampPixelFloatLinear(e.b)}}};
+}
+
+static inline Color sanitizePixel(Color e) {
+  float r = std::isfinite(e.r) ? clampPixelFloatLinear(e.r) : 0.0f;
+  float g = std::isfinite(e.g) ? clampPixelFloatLinear(e.g) : 0.0f;
+  float b = std::isfinite(e.b) ? clampPixelFloatLinear(e.b) : 0.0f;
+  return {{{r, g, b}}};
+}
+
 bool isPixelFormatRgb(uhdr_img_fmt_t format);
 
 uint32_t colorToRgba1010102(Color e_gamma);
diff --git a/lib/include/ultrahdr/jpegr.h b/lib/include/ultrahdr/jpegr.h
index 248d4864..466b3897 100644
--- a/lib/include/ultrahdr/jpegr.h
+++ b/lib/include/ultrahdr/jpegr.h
@@ -418,7 +418,6 @@ class JpegR {
   uhdr_error_info_t parseGainMapMetadata(uint8_t* iso_data, int iso_size, uint8_t* xmp_data,
                                          int xmp_size, uhdr_gainmap_metadata_ext_t* uhdr_metadata);
 
-
   /*!\brief This method is used to tone map a hdr image
    *
    * \param[in]            hdr_intent      hdr image descriptor
@@ -608,20 +607,28 @@ class JpegR {
   float mTargetDispPeakBrightness;  // target display max luminance in nits
 };
 
+/*
+ * Holds tonemapping results of a pixel
+ */
 struct GlobalTonemapOutputs {
   std::array rgb_out;
   float y_hdr;
   float y_sdr;
 };
 
-// Applies a global tone mapping, based on Chrome's HLG/PQ rendering implemented
-// at
-// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=1198-1232;drc=ac505aff1d29ec3bfcf317cb77d5e196a3664e92
-// `rgb_in` is expected to be in the normalized range of [0.0, 1.0] and
-// `rgb_out` is returned in this same range. `headroom` describes the ratio
-// between the HDR and SDR peak luminances and must be > 1. The `y_sdr` output
-// is in the range [0.0, 1.0] while `y_hdr` is in the range [0.0, headroom].
-GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom);
+/*!\brief Applies a global tone mapping, based on Chrome's HLG/PQ rendering implemented at
+ *  https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/color_transform.cc;l=1197-1252;drc=ac505aff1d29ec3bfcf317cb77d5e196a3664e92
+ *
+ * \param[in]       rgb_in              hdr intent pixel in array format.
+ * \param[in]       headroom            ratio between hdr and sdr peak luminances. Must be greater
+ *                                      than 1. If the input is normalized, then this is used to
+ *                                      stretch it linearly from [0.0..1.0] to [0.0..headroom]
+ * \param[in]       is_normalized       marker to differentiate, if the input is normalized.
+ *
+ * \return tonemapped pixel in the normalized range [0.0..1.0]
+ */
+GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom,
+                                   bool is_normalized);
 
 }  // namespace ultrahdr
 
diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp
index a54bfc6b..202970be 100644
--- a/lib/src/gainmapmath.cpp
+++ b/lib/src/gainmapmath.cpp
@@ -463,6 +463,17 @@ Color getRgba1010102Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
   return pixel / 1023.0f;
 }
 
+Color getRgbaF16Pixel(uhdr_raw_image_t* image, size_t x, size_t y) {
+  uint64_t* rgbData = static_cast(image->planes[UHDR_PLANE_PACKED]);
+  unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
+
+  Color pixel;
+  pixel.r = halfToFloat(rgbData[x + y * srcStride] & 0xffff);
+  pixel.g = halfToFloat((rgbData[x + y * srcStride] >> 16) & 0xffff);
+  pixel.b = halfToFloat((rgbData[x + y * srcStride] >> 32) & 0xffff);
+  return sanitizePixel(pixel);
+}
+
 static Color samplePixels(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y,
                           GetPixelFn get_pixel_fn) {
   Color e = {{{0.0f, 0.0f, 0.0f}}};
@@ -503,6 +514,10 @@ Color sampleRgba1010102(uhdr_raw_image_t* image, size_t map_scale_factor, size_t
   return samplePixels(image, map_scale_factor, x, y, getRgba1010102Pixel);
 }
 
+Color sampleRgbaF16(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) {
+  return samplePixels(image, map_scale_factor, x, y, getRgbaF16Pixel);
+}
+
 void putRgba8888Pixel(uhdr_raw_image_t* image, size_t x, size_t y, Color& pixel) {
   uint32_t* rgbData = static_cast(image->planes[UHDR_PLANE_PACKED]);
   unsigned int srcStride = image->stride[UHDR_PLANE_PACKED];
@@ -1163,6 +1178,8 @@ GetPixelFn getPixelFn(uhdr_img_fmt_t format) {
       return getRgba8888Pixel;
     case UHDR_IMG_FMT_32bppRGBA1010102:
       return getRgba1010102Pixel;
+    case UHDR_IMG_FMT_64bppRGBAHalfFloat:
+      return getRgbaF16Pixel;
     default:
       return nullptr;
   }
@@ -1197,6 +1214,8 @@ SamplePixelFn getSamplePixelFn(uhdr_img_fmt_t format) {
       return sampleRgba8888;
     case UHDR_IMG_FMT_32bppRGBA1010102:
       return sampleRgba1010102;
+    case UHDR_IMG_FMT_64bppRGBAHalfFloat:
+      return sampleRgbaF16;
     default:
       return nullptr;
   }
diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp
index 888a570f..dbcb80ca 100644
--- a/lib/src/jpegr.cpp
+++ b/lib/src/jpegr.cpp
@@ -190,7 +190,8 @@ uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_compress
     sdr_intent_fmt = UHDR_IMG_FMT_12bppYCbCr420;
   } else if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
     sdr_intent_fmt = UHDR_IMG_FMT_24bppYCbCr444;
-  } else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+  } else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
+             hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
     sdr_intent_fmt = UHDR_IMG_FMT_32bppRGBA8888;
   } else {
     uhdr_error_info_t status;
@@ -525,13 +526,14 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
   }
   if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
       hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 &&
-      hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102) {
+      hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
+      hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
     status.has_detail = 1;
     snprintf(status.detail, sizeof status.detail,
              "generate gainmap method expects hdr intent color format to be one of "
              "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, "
-             "UHDR_IMG_FMT_32bppRGBA1010102}. Received %d",
+             "UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d",
              hdr_intent->fmt);
     return status;
   }
@@ -683,7 +685,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
     if (this->mTargetDispPeakBrightness != -1.0f) {
       gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits;
     } else {
-      gainmap_metadata->hdr_capacity_max = hdr_white_nits / kSdrWhiteNits;
+      gainmap_metadata->hdr_capacity_max = gainmap_metadata->max_content_boost;
     }
 
     float log2MinBoost = log2(gainmap_metadata->min_content_boost);
@@ -701,6 +703,8 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
       size_t rowStart, rowEnd;
       const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
       const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
+      const float hdrSampleToNitsFactor =
+          hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
       while (jobQueue.dequeueJob(rowStart, rowEnd)) {
         for (size_t y = rowStart; y < rowEnd; ++y) {
           for (size_t x = 0; x < dest->w; ++x) {
@@ -734,7 +738,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
 
             if (mUseMultiChannelGainMap) {
               Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
-              Color hdr_rgb_nits = hdr_rgb * hdr_white_nits;
+              Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor;
               size_t pixel_idx = (x + y * dest->stride[UHDR_PLANE_PACKED]) * 3;
 
               reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = encodeGain(
@@ -750,10 +754,10 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
               float hdr_y_nits;
               if (use_luminance) {
                 sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-                hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+                hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor;
               } else {
                 sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits;
-                hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdr_white_nits;
+                hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor;
               }
 
               size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_Y];
@@ -805,6 +809,8 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
       size_t rowStart, rowEnd;
       const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
       const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
+      const float hdrSampleToNitsFactor =
+          hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits;
       float gainmap_min_th[3] = {127.0f, 127.0f, 127.0f};
       float gainmap_max_th[3] = {-128.0f, -128.0f, -128.0f};
 
@@ -841,7 +847,7 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
 
             if (mUseMultiChannelGainMap) {
               Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits;
-              Color hdr_rgb_nits = hdr_rgb * hdr_white_nits;
+              Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor;
               size_t pixel_idx = (x + y * map_width) * 3;
 
               gainmap_data[pixel_idx] = computeGain(sdr_rgb_nits.r, hdr_rgb_nits.r);
@@ -857,10 +863,10 @@ uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_
 
               if (use_luminance) {
                 sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-                hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+                hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor;
               } else {
                 sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits;
-                hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdr_white_nits;
+                hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor;
               }
 
               size_t pixel_idx = x + y * map_width;
@@ -1702,12 +1708,13 @@ static float ReinhardMap(float y_hdr, float headroom) {
   return out * y_hdr;
 }
 
-GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom) {
+GlobalTonemapOutputs globalTonemap(const std::array& rgb_in, float headroom,
+                                   bool is_normalized) {
   // Scale to Headroom to get HDR values that are referenced to SDR white. The range [0.0, 1.0] is
   // linearly stretched to [0.0, headroom].
   std::array rgb_hdr;
   std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(),
-                 [&](float x) { return x * headroom; });
+                 [&](float x) { return is_normalized ? x * headroom : x; });
 
   // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by
   // keeping the shadows the same and crushing the highlights.
@@ -1738,15 +1745,16 @@ uint8_t ScaleTo8Bit(float value) {
 uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent) {
   if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 &&
       hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 &&
-      hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102) {
+      hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 &&
+      hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) {
     uhdr_error_info_t status;
     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
     status.has_detail = 1;
-    snprintf(
-        status.detail, sizeof status.detail,
-        "tonemap method expects hdr intent color format to be one of {UHDR_IMG_FMT_24bppYCbCrP010, "
-        "UHDR_IMG_FMT_30bppYCbCr444, UHDR_IMG_FMT_32bppRGBA1010102}. Received %d",
-        hdr_intent->fmt);
+    snprintf(status.detail, sizeof status.detail,
+             "tonemap method expects hdr intent color format to be one of "
+             "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, "
+             "UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d",
+             hdr_intent->fmt);
     return status;
   }
 
@@ -1774,14 +1782,16 @@ uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t*
     return status;
   }
 
-  if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 &&
+  if ((hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
+       hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) &&
       sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) {
     uhdr_error_info_t status;
     status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
     status.has_detail = 1;
     snprintf(status.detail, sizeof status.detail,
              "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_32bppRGBA8888, if "
-             "hdr intent color format is UHDR_IMG_FMT_32bppRGBA1010102. Received %d",
+             "hdr intent color format is UHDR_IMG_FMT_32bppRGBA1010102 or "
+             "UHDR_IMG_FMT_64bppRGBAHalfFloat. Received %d",
              sdr_intent->fmt);
     return status;
   }
@@ -1886,6 +1896,7 @@ uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t*
     const int vfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1;
     const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt);
     const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt);
+    const bool is_normalized = hdr_intent->ct != UHDR_CT_LINEAR;
     uint8_t* luma_data = reinterpret_cast(sdr_intent->planes[UHDR_PLANE_Y]);
     uint8_t* cb_data = reinterpret_cast(sdr_intent->planes[UHDR_PLANE_U]);
     uint8_t* cr_data = reinterpret_cast(sdr_intent->planes[UHDR_PLANE_V]);
@@ -1913,8 +1924,8 @@ uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t*
               Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
               hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn);
 
-              GlobalTonemapOutputs tonemap_outputs =
-                  globalTonemap({hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits);
+              GlobalTonemapOutputs tonemap_outputs = globalTonemap(
+                  {hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits, is_normalized);
               Color sdr_rgb_linear_bt2100 = {
                   {{tonemap_outputs.rgb_out[0], tonemap_outputs.rgb_out[1],
                     tonemap_outputs.rgb_out[2]}}};
diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp
index f05718ec..7cbc4ad8 100644
--- a/lib/src/ultrahdr_api.cpp
+++ b/lib/src/ultrahdr_api.cpp
@@ -826,12 +826,14 @@ uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_ima
     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_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_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)) {
@@ -854,14 +856,22 @@ uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_ima
     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->ct != UHDR_CT_HLG && img->ct != UHDR_CT_LINEAR && img->ct != UHDR_CT_PQ)) {
+  } 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, expects one of {UHDR_CT_HLG, "
-             "UHDR_CT_LINEAR, UHDR_CT_PQ}",
+             "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;
@@ -945,7 +955,8 @@ uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_ima
       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) {
+  } 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;
diff --git a/ultrahdr_api.h b/ultrahdr_api.h
index b715602e..f3d1128d 100644
--- a/ultrahdr_api.h
+++ b/ultrahdr_api.h
@@ -100,20 +100,23 @@ typedef enum uhdr_img_fmt {
       3, /**< 32 bits per pixel RGBA color format, with 8-bit red, green, blue
         and alpha components. Using 32-bit little-endian representation,
         colors stored as Red 7:0, Green 15:8, Blue 23:16, Alpha 31:24. */
-  UHDR_IMG_FMT_64bppRGBAHalfFloat = 4, /**< 64 bits per pixel RGBA color format, with 16-bit signed
-                                   floating point red, green, blue, and alpha components */
-  UHDR_IMG_FMT_32bppRGBA1010102 = 5,   /**< 32 bits per pixel RGBA color format, with 10-bit red,
-                                      green,   blue, and 2-bit alpha components. Using 32-bit
-                                      little-endian   representation, colors stored as Red 9:0, Green
-                                      19:10, Blue   29:20, and Alpha 31:30. */
-  UHDR_IMG_FMT_24bppYCbCr444 = 6,      /**< 8-bit-per component 4:4:4 YCbCr planar format */
-  UHDR_IMG_FMT_16bppYCbCr422 = 7,      /**< 8-bit-per component 4:2:2 YCbCr planar format */
-  UHDR_IMG_FMT_16bppYCbCr440 = 8,      /**< 8-bit-per component 4:4:0 YCbCr planar format */
-  UHDR_IMG_FMT_12bppYCbCr411 = 9,      /**< 8-bit-per component 4:1:1 YCbCr planar format */
-  UHDR_IMG_FMT_10bppYCbCr410 = 10,     /**< 8-bit-per component 4:1:0 YCbCr planar format */
-  UHDR_IMG_FMT_24bppRGB888 = 11,       /**< 8-bit-per component RGB interleaved format */
-  UHDR_IMG_FMT_30bppYCbCr444 = 12,     /**< 10-bit-per component 4:4:4 YCbCr planar format */
-} uhdr_img_fmt_t;                      /**< alias for enum uhdr_img_fmt */
+  UHDR_IMG_FMT_64bppRGBAHalfFloat =
+      4, /**< 64 bits per pixel, 16 bits per channel, half-precision floating point RGBA color
+            format. colors stored as Red 15:0, Green 31:16, Blue 47:32, Alpha 63:48. In a pixel
+            even though each channel has storage space of 16 bits, the nominal range is expected to
+            be [0.0..(10000/203)] */
+  UHDR_IMG_FMT_32bppRGBA1010102 = 5, /**< 32 bits per pixel RGBA color format, with 10-bit red,
+                                    green,   blue, and 2-bit alpha components. Using 32-bit
+                                    little-endian   representation, colors stored as Red 9:0, Green
+                                    19:10, Blue   29:20, and Alpha 31:30. */
+  UHDR_IMG_FMT_24bppYCbCr444 = 6,    /**< 8-bit-per component 4:4:4 YCbCr planar format */
+  UHDR_IMG_FMT_16bppYCbCr422 = 7,    /**< 8-bit-per component 4:2:2 YCbCr planar format */
+  UHDR_IMG_FMT_16bppYCbCr440 = 8,    /**< 8-bit-per component 4:4:0 YCbCr planar format */
+  UHDR_IMG_FMT_12bppYCbCr411 = 9,    /**< 8-bit-per component 4:1:1 YCbCr planar format */
+  UHDR_IMG_FMT_10bppYCbCr410 = 10,   /**< 8-bit-per component 4:1:0 YCbCr planar format */
+  UHDR_IMG_FMT_24bppRGB888 = 11,     /**< 8-bit-per component RGB interleaved format */
+  UHDR_IMG_FMT_30bppYCbCr444 = 12,   /**< 10-bit-per component 4:4:4 YCbCr planar format */
+} uhdr_img_fmt_t;                    /**< alias for enum uhdr_img_fmt */
 
 /*!\brief List of supported color gamuts */
 typedef enum uhdr_color_gamut {