Add Gainmap API

Bug: 266628247
Test: CtsGraphicsTestCases:GainmapTest
Change-Id: I288516df8ba2bb72525462699d2704bb43a0fd90
diff --git a/core/api/current.txt b/core/api/current.txt
index de540bf..6c2ba6d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14779,6 +14779,7 @@
     method @Nullable public android.graphics.ColorSpace getColorSpace();
     method @NonNull public android.graphics.Bitmap.Config getConfig();
     method public int getDensity();
+    method @Nullable public android.graphics.Gainmap getGainmap();
     method public int getGenerationId();
     method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
     method public int getHeight();
@@ -14794,6 +14795,7 @@
     method public int getScaledWidth(int);
     method public int getWidth();
     method public boolean hasAlpha();
+    method public boolean hasGainmap();
     method public boolean hasMipMap();
     method public boolean isMutable();
     method public boolean isPremultiplied();
@@ -14805,6 +14807,7 @@
     method public void setColorSpace(@NonNull android.graphics.ColorSpace);
     method public void setConfig(@NonNull android.graphics.Bitmap.Config);
     method public void setDensity(int);
+    method public void setGainmap(@Nullable android.graphics.Gainmap);
     method public void setHasAlpha(boolean);
     method public void setHasMipMap(boolean);
     method public void setHeight(int);
@@ -15338,6 +15341,26 @@
     ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
   }
 
+  public final class Gainmap {
+    ctor public Gainmap(@NonNull android.graphics.Bitmap);
+    method @NonNull public float getDisplayRatioForFullHdr();
+    method @NonNull public float[] getEpsilonHdr();
+    method @NonNull public float[] getEpsilonSdr();
+    method @NonNull public android.graphics.Bitmap getGainmapContents();
+    method @NonNull public float[] getGamma();
+    method @NonNull public float getMinDisplayRatioForHdrTransition();
+    method @NonNull public float[] getRatioMax();
+    method @NonNull public float[] getRatioMin();
+    method @NonNull public void setDisplayRatioForFullHdr(float);
+    method @NonNull public void setEpsilonHdr(float, float, float);
+    method @NonNull public void setEpsilonSdr(float, float, float);
+    method public void setGainmapContents(@NonNull android.graphics.Bitmap);
+    method @NonNull public void setGamma(float, float, float);
+    method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
+    method @NonNull public void setRatioMax(float, float, float);
+    method @NonNull public void setRatioMin(float, float, float);
+  }
+
   public class HardwareBufferRenderer implements java.lang.AutoCloseable {
     ctor public HardwareBufferRenderer(@NonNull android.hardware.HardwareBuffer);
     method public void close();
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 046373d..b1abc2a1 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -1899,7 +1899,6 @@
 
     /**
      * Returns whether or not this Bitmap contains a Gainmap.
-     * @hide
      */
     public boolean hasGainmap() {
         checkRecycled("Bitmap is recycled");
@@ -1908,7 +1907,6 @@
 
     /**
      * Returns the gainmap or null if the bitmap doesn't contain a gainmap
-     * @hide
      */
     public @Nullable Gainmap getGainmap() {
         checkRecycled("Bitmap is recycled");
@@ -1919,6 +1917,14 @@
     }
 
     /**
+     * Sets a gainmap on this bitmap, or removes the gainmap if null
+     */
+    public void setGainmap(@Nullable Gainmap gainmap) {
+        checkRecycled("Bitmap is recycled");
+        nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr);
+    }
+
+    /**
      * Fills the bitmap's pixels with the specified {@link Color}.
      *
      * @throws IllegalStateException if the bitmap is not mutable.
@@ -2403,6 +2409,7 @@
     private static native void nativeSetImmutable(long nativePtr);
 
     private static native Gainmap nativeExtractGainmap(long nativePtr);
+    private static native void nativeSetGainmap(long bitmapPtr, long gainmapPtr);
 
     // ---------------- @CriticalNative -------------------
 
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index a25a605..53f23c0 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -16,6 +16,7 @@
 
 package android.graphics;
 
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
 
 import libcore.util.NativeAllocationRegistry;
@@ -24,104 +25,288 @@
  * Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
  * display adjustment capability.
  *
- * It is a combination of a set of metadata describing the gainmap, as well as either a 1 or 3
+ * It is a combination of a set of metadata describing how to apply the gainmap, as well as either
+ * a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
+ * (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
  * channel Bitmap that represents the gainmap data itself.
  *
- * @hide
+ * When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
+ * hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
+ * HDR headroom is available.
+ *
+ * <h3>Gainmap Structure</h3>
+ *
+ * The logical whole of a gainmap'd image consists of a base Bitmap that represents the original
+ * image as would be displayed without gainmap support in addition to a gainmap with a second
+ * enhancement image. In the case of a JPEG, the base image would be the typical 8-bit SDR image
+ * that the format is commonly associated with. The gainmap image is embedded alongside the base
+ * image, often at a lower resolution (such as 1/4th), along with some metadata to describe
+ * how to apply the gainmap. The gainmap image itself is then a greyscale image representing
+ * the transformation to apply onto the base image to reconstruct an HDR rendition of it.
+ *
+ * As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
+ * {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
+ * the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
+ *
+ * <h3>Applying a gainmap manually</h3>
+ *
+ * When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
+ * automatically applied. In such situations, the following steps are appropriate to render the
+ * gainmap in combination with the base image.
+ *
+ * Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
+ * this display. Let B be the pixel value from the base image in a color space that has the
+ * primaries of the base image and a linear transfer function. Let G be the pixel value from the
+ * gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
+ * as follows:
+ *
+ * First, let W be a weight parameter determining how much the gainmap will be applied.
+ *   W = clamp((log(H)               - log(displayRatioHdr)) /
+ *             (log(displayRatioHdr) - log(displayRatioSdr), 0, 1)
+ *
+ * Next, let L be the gainmap value in log space. We compute this from the value G that was
+ * sampled from the texture as follows:
+ *   L = mix(log(gainmapRatioMin), log(gainmapRatioMax), pow(G, gainmapGamma))
+ *
+ * Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
+ * compute:
+ *   D = (B + epsilonSdr) * exp(L * W) - epsilonHdr
+ * If the base image is HDR then compute:
+ *   D = (B + epsilonHdr) * exp(L * (W - 1)) - epsilonSdr
+ *
+ * In the above math, log() is a natural logarithm and exp() is natural exponentiation.
  */
-public class Gainmap {
-    private final long mNativePtr;
-    private final Bitmap mGainmapImage;
+public final class Gainmap {
 
-    // called from JNI and Bitmap_Delegate.
-    private Gainmap(Bitmap gainmapImage, long nativeGainmap, int allocationByteCount,
-            boolean fromMalloc) {
+    // Use a Holder to allow static initialization of Gainmap in the boot image.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry =
+                NativeAllocationRegistry.createMalloced(
+                        Gainmap.class.getClassLoader(), nGetFinalizer());
+    }
+
+    final long mNativePtr;
+    private Bitmap mGainmapContents;
+
+    // called from JNI
+    private Gainmap(Bitmap gainmapContents, long nativeGainmap) {
         if (nativeGainmap == 0) {
             throw new RuntimeException("internal error: native gainmap is 0");
         }
 
-        mGainmapImage = gainmapImage;
+        mGainmapContents = gainmapContents;
         mNativePtr = nativeGainmap;
 
-        final NativeAllocationRegistry registry;
-        if (fromMalloc) {
-            registry = NativeAllocationRegistry.createMalloced(
-                    Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount);
-        } else {
-            registry = NativeAllocationRegistry.createNonmalloced(
-                    Bitmap.class.getClassLoader(), nGetFinalizer(), allocationByteCount);
-        }
-        registry.registerNativeAllocation(this, nativeGainmap);
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, nativeGainmap);
     }
 
     /**
-     * Returns the image data of the gainmap represented as a Bitmap
-     * @return
+     * Creates a gainmap from a given Bitmap. The caller is responsible for setting the various
+     * fields to the desired values. The defaults are as follows:
+     * <ul>
+     *     <li>Ratio min is 1f, 1f, 1f</li>
+     *     <li>Ratio max is 2f, 2f, 2f</li>
+     *     <li>Gamma is 1f, 1f, 1f</li>
+     *     <li>Epsilon SDR is 0f, 0f, 0f</li>
+     *     <li>Epsilon HDR is 0f, 0f, 0f</li>
+     *     <li>Display ratio SDR is 1f</li>
+     *     <li>Display ratio HDR is 2f</li>
+     * </ul>
+     * It is strongly recommended that at least the ratio max and display ratio HDR are adjusted
+     * to better suit the given gainmap contents.
+     */
+    public Gainmap(@NonNull Bitmap gainmapContents) {
+        this(gainmapContents, nCreateEmpty());
+    }
+
+    /**
+     * @return Returns the image data of the gainmap represented as a Bitmap. This is represented
+     * as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored
+     * such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not
+     * relevant to the gainmap's enhancement layer.
      */
     @NonNull
-    public Bitmap getGainmapImage() {
-        return mGainmapImage;
+    public Bitmap getGainmapContents() {
+        return mGainmapContents;
     }
 
     /**
-     * Sets the gainmap max metadata. For single-plane gainmaps, r, g, and b should be the same.
+     * Sets the image data of the gainmap. This is the 1 or 3 channel enhancement layer to apply
+     * to the base image. This is represented as a Bitmap for broad API compatibility, however
+     * certain aspects of the Bitmap are ignored such as {@link Bitmap#getColorSpace()} or
+     * {@link Bitmap#getGainmap()} as they are not relevant to the gainmap's enhancement layer.
+     *
+     * @param bitmap The non-null bitmap to set as the gainmap's contents
+     */
+    public void setGainmapContents(@NonNull Bitmap bitmap) {
+        // TODO: Validate here or leave native-side?
+        if (bitmap.isRecycled()) throw new IllegalArgumentException("Bitmap is recycled");
+        nSetBitmap(mNativePtr, bitmap);
+        mGainmapContents = bitmap;
+    }
+
+    /**
+     * Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
      */
     @NonNull
-    public void setGainmapMax(float r, float g, float b) {
-        nSetGainmapMax(mNativePtr, r, g, b);
+    public void setRatioMin(float r, float g, float b) {
+        nSetRatioMin(mNativePtr, r, g, b);
     }
 
     /**
-     * Gets the gainmap max metadata. For single-plane gainmaps, all 3 components should be the
+     * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
      * same. The components are in r, g, b order.
      */
     @NonNull
-    public float[] getGainmapMax() {
+    public float[] getRatioMin() {
         float[] ret = new float[3];
-        nGetGainmapMax(mNativePtr, ret);
+        nGetRatioMin(mNativePtr, ret);
         return ret;
     }
 
     /**
-     * Sets the maximum HDR ratio for the gainmap
+     * Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
      */
     @NonNull
-    public void setHdrRatioMax(float max) {
-        nSetHdrRatioMax(mNativePtr, max);
+    public void setRatioMax(float r, float g, float b) {
+        nSetRatioMax(mNativePtr, r, g, b);
     }
 
     /**
-     * Gets the maximum HDR ratio for the gainmap
+     * Gets the gainmap ratio max. For single-plane gainmaps, all 3 components should be the
+     * same. The components are in r, g, b order.
      */
     @NonNull
-    public float getHdrRatioMax() {
-        return nGetHdrRatioMax(mNativePtr);
+    public float[] getRatioMax() {
+        float[] ret = new float[3];
+        nGetRatioMax(mNativePtr, ret);
+        return ret;
     }
 
     /**
-     * Sets the maximum HDR ratio for the gainmap
+     * Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
      */
     @NonNull
-    public void setHdrRatioMin(float min) {
-        nSetHdrRatioMin(mNativePtr, min);
+    public void setGamma(float r, float g, float b) {
+        nSetGamma(mNativePtr, r, g, b);
     }
 
     /**
-     * Gets the maximum HDR ratio for the gainmap
+     * Gets the gainmap gamma. For single-plane gainmaps, all 3 components should be the
+     * same. The components are in r, g, b order.
      */
     @NonNull
-    public float getHdrRatioMin() {
-        return nGetHdrRatioMin(mNativePtr);
+    public float[] getGamma() {
+        float[] ret = new float[3];
+        nGetGamma(mNativePtr, ret);
+        return ret;
+    }
+
+    /**
+     * Sets the sdr epsilon which is used to avoid numerical instability.
+     * For single-plane gainmaps, r, g, and b should be the same.
+     */
+    @NonNull
+    public void setEpsilonSdr(float r, float g, float b) {
+        nSetEpsilonSdr(mNativePtr, r, g, b);
+    }
+
+    /**
+     * Gets the sdr epsilon. For single-plane gainmaps, all 3 components should be the
+     * same. The components are in r, g, b order.
+     */
+    @NonNull
+    public float[] getEpsilonSdr() {
+        float[] ret = new float[3];
+        nGetEpsilonSdr(mNativePtr, ret);
+        return ret;
+    }
+
+    /**
+     * Sets the hdr epsilon which is used to avoid numerical instability.
+     * For single-plane gainmaps, r, g, and b should be the same.
+     */
+    @NonNull
+    public void setEpsilonHdr(float r, float g, float b) {
+        nSetEpsilonHdr(mNativePtr, r, g, b);
+    }
+
+    /**
+     * Gets the hdr epsilon. For single-plane gainmaps, all 3 components should be the
+     * same. The components are in r, g, b order.
+     */
+    @NonNull
+    public float[] getEpsilonHdr() {
+        float[] ret = new float[3];
+        nGetEpsilonHdr(mNativePtr, ret);
+        return ret;
+    }
+
+    /**
+     * Sets the hdr/sdr ratio at which point the gainmap is fully applied.
+     * @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
+     */
+    @NonNull
+    public void setDisplayRatioForFullHdr(float max) {
+        if (!Float.isFinite(max) || max < 1f) {
+            throw new IllegalArgumentException(
+                    "setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
+        }
+        nSetDisplayRatioHdr(mNativePtr, max);
+    }
+
+    /**
+     * Gets the hdr/sdr ratio at which point the gainmap is fully applied.
+     */
+    @NonNull
+    public float getDisplayRatioForFullHdr() {
+        return nGetDisplayRatioHdr(mNativePtr);
+    }
+
+    /**
+     * Sets the hdr/sdr ratio below which only the SDR image is displayed.
+     * @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
+     */
+    @NonNull
+    public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
+        if (!Float.isFinite(min) || min < 1f) {
+            throw new IllegalArgumentException(
+                    "setMinDisplayRatioForHdrTransition must be >= 1.0f, got = " + min);
+        }
+        nSetDisplayRatioSdr(mNativePtr, min);
+    }
+
+    /**
+     * Gets the hdr/sdr ratio below which only the SDR image is displayed.
+     */
+    @NonNull
+    public float getMinDisplayRatioForHdrTransition() {
+        return nGetDisplayRatioSdr(mNativePtr);
     }
 
     private static native long nGetFinalizer();
+    private static native long nCreateEmpty();
 
-    private static native void nSetGainmapMax(long ptr, float r, float g, float b);
-    private static native void nGetGainmapMax(long ptr, float[] components);
+    private static native void nSetBitmap(long ptr, Bitmap bitmap);
 
-    private static native void nSetHdrRatioMax(long ptr, float max);
-    private static native float nGetHdrRatioMax(long ptr);
+    private static native void nSetRatioMin(long ptr, float r, float g, float b);
+    private static native void nGetRatioMin(long ptr, float[] components);
 
-    private static native void nSetHdrRatioMin(long ptr, float min);
-    private static native float nGetHdrRatioMin(long ptr);
+    private static native void nSetRatioMax(long ptr, float r, float g, float b);
+    private static native void nGetRatioMax(long ptr, float[] components);
+
+    private static native void nSetGamma(long ptr, float r, float g, float b);
+    private static native void nGetGamma(long ptr, float[] components);
+
+    private static native void nSetEpsilonSdr(long ptr, float r, float g, float b);
+    private static native void nGetEpsilonSdr(long ptr, float[] components);
+
+    private static native void nSetEpsilonHdr(long ptr, float r, float g, float b);
+    private static native void nGetEpsilonHdr(long ptr, float[] components);
+
+    private static native void nSetDisplayRatioHdr(long ptr, float max);
+    private static native float nGetDisplayRatioHdr(long ptr);
+
+    private static native void nSetDisplayRatioSdr(long ptr, float min);
+    private static native float nGetDisplayRatioSdr(long ptr);
 }
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 6d7c727..5bf53d0 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -506,6 +506,9 @@
     decoder.mOverrideOrigin.emplace(getOrigin());
     // Update mDecodeSize / mTargetSize for the overridden origin
     decoder.setTargetSize(decoder.width(), decoder.height());
+    if (decoder.gray()) {
+        decoder.setOutColorType(kGray_8_SkColorType);
+    }
 
     const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height();
 
@@ -528,6 +531,9 @@
     }
 
     SkImageInfo bitmapInfo = decoder.getOutputInfo();
+    if (bitmapInfo.colorType() == kGray_8_SkColorType) {
+        bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+    }
 
     SkBitmap bm;
     if (!bm.setInfo(bitmapInfo)) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 10c287d..aee5242 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1269,6 +1269,13 @@
     return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap());
 }
 
+static void Bitmap_setGainmap(JNIEnv*, jobject, jlong bitmapHandle, jlong gainmapPtr) {
+    LocalScopedBitmap bitmapHolder(bitmapHandle);
+    if (!bitmapHolder.valid()) return;
+    uirenderer::Gainmap* gainmap = reinterpret_cast<uirenderer::Gainmap*>(gainmapPtr);
+    bitmapHolder->bitmap().setGainmap(sp<uirenderer::Gainmap>::fromExisting(gainmap));
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gBitmapMethods[] = {
@@ -1320,6 +1327,7 @@
         {"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
         {"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
         {"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap},
+        {"nativeSetGainmap", "(JJ)V", (void*)Bitmap_setGainmap},
 
         // ------------ @CriticalNative ----------------
         {"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable},
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index f2efbc7..9cd3fb0 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -45,20 +45,18 @@
 jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap) {
     auto gainmap = bitmap.gainmap();
     jobject jGainmapImage;
-    size_t allocationSize;
 
     {
         // Scope to guard the release of nativeBitmap
         auto nativeBitmap = gainmap->bitmap;
         const int createFlags = getCreateFlags(nativeBitmap);
-        allocationSize = nativeBitmap->getAllocationByteCount();
         jGainmapImage = bitmap::createBitmap(env, nativeBitmap.release(), createFlags);
     }
 
     // Grab a ref for the jobject
     gainmap->incStrong(0);
     jobject obj = env->NewObject(gGainmap_class, gGainmap_constructorMethodID, jGainmapImage,
-                                 gainmap.get(), allocationSize + sizeof(Gainmap), true);
+                                 gainmap.get());
 
     if (env->ExceptionCheck() != 0) {
         // sadtrombone
@@ -77,47 +75,109 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Gainmap_destructor));
 }
 
-static void Gainmap_setGainmapMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
-                                  jfloat b) {
-    fromJava(gainmapPtr)->info.fLogRatioMax = {r, g, b, 1.f};
+jlong Gainmap_createEmpty(JNIEnv*, jobject) {
+    Gainmap* gainmap = new Gainmap();
+    gainmap->incStrong(0);
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
 }
 
-static void Gainmap_getGainmapMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
-    const auto ratioMax = fromJava(gainmapPtr)->info.fLogRatioMax;
-    jfloat buf[3]{ratioMax.fR, ratioMax.fG, ratioMax.fB};
+static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
+    android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
+    fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
+}
+
+static void Gainmap_setRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+    fromJava(gainmapPtr)->info.fGainmapRatioMin = {r, g, b, 1.f};
+}
+
+static void Gainmap_getRatioMin(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+    const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMin;
+    jfloat buf[3]{value.fR, value.fG, value.fB};
     env->SetFloatArrayRegion(components, 0, 3, buf);
 }
 
-static void Gainmap_setHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
-    fromJava(gainmapPtr)->info.fHdrRatioMax = max;
+static void Gainmap_setRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+    fromJava(gainmapPtr)->info.fGainmapRatioMax = {r, g, b, 1.f};
 }
 
-static jfloat Gainmap_getHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr) {
-    return fromJava(gainmapPtr)->info.fHdrRatioMax;
+static void Gainmap_getRatioMax(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+    const auto value = fromJava(gainmapPtr)->info.fGainmapRatioMax;
+    jfloat buf[3]{value.fR, value.fG, value.fB};
+    env->SetFloatArrayRegion(components, 0, 3, buf);
 }
 
-static void Gainmap_setHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
-    fromJava(gainmapPtr)->info.fHdrRatioMin = min;
+static void Gainmap_setGamma(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g, jfloat b) {
+    fromJava(gainmapPtr)->info.fGainmapGamma = {r, g, b, 1.f};
 }
 
-static jfloat Gainmap_getHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr) {
-    return fromJava(gainmapPtr)->info.fHdrRatioMin;
+static void Gainmap_getGamma(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+    const auto value = fromJava(gainmapPtr)->info.fGainmapGamma;
+    jfloat buf[3]{value.fR, value.fG, value.fB};
+    env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setEpsilonSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
+                                  jfloat b) {
+    fromJava(gainmapPtr)->info.fEpsilonSdr = {r, g, b, 1.f};
+}
+
+static void Gainmap_getEpsilonSdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+    const auto value = fromJava(gainmapPtr)->info.fEpsilonSdr;
+    jfloat buf[3]{value.fR, value.fG, value.fB};
+    env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setEpsilonHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat r, jfloat g,
+                                  jfloat b) {
+    fromJava(gainmapPtr)->info.fEpsilonHdr = {r, g, b, 1.f};
+}
+
+static void Gainmap_getEpsilonHdr(JNIEnv* env, jobject, jlong gainmapPtr, jfloatArray components) {
+    const auto value = fromJava(gainmapPtr)->info.fEpsilonHdr;
+    jfloat buf[3]{value.fR, value.fG, value.fB};
+    env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
+    fromJava(gainmapPtr)->info.fDisplayRatioHdr = max;
+}
+
+static jfloat Gainmap_getDisplayRatioHdr(JNIEnv*, jobject, jlong gainmapPtr) {
+    return fromJava(gainmapPtr)->info.fDisplayRatioHdr;
+}
+
+static void Gainmap_setDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
+    fromJava(gainmapPtr)->info.fDisplayRatioSdr = min;
+}
+
+static jfloat Gainmap_getDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr) {
+    return fromJava(gainmapPtr)->info.fDisplayRatioSdr;
 }
 
 static const JNINativeMethod gGainmapMethods[] = {
         {"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
-        {"nSetGainmapMax", "(JFFF)V", (void*)Gainmap_setGainmapMax},
-        {"nGetGainmapMax", "(J[F)V", (void*)Gainmap_getGainmapMax},
-        {"nSetHdrRatioMax", "(JF)V", (void*)Gainmap_setHdrRatioMax},
-        {"nGetHdrRatioMax", "(J)F", (void*)Gainmap_getHdrRatioMax},
-        {"nSetHdrRatioMin", "(JF)V", (void*)Gainmap_setHdrRatioMin},
-        {"nGetHdrRatioMin", "(J)F", (void*)Gainmap_getHdrRatioMin},
+        {"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
+        {"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
+        {"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
+        {"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
+        {"nSetRatioMax", "(JFFF)V", (void*)Gainmap_setRatioMax},
+        {"nGetRatioMax", "(J[F)V", (void*)Gainmap_getRatioMax},
+        {"nSetGamma", "(JFFF)V", (void*)Gainmap_setGamma},
+        {"nGetGamma", "(J[F)V", (void*)Gainmap_getGamma},
+        {"nSetEpsilonSdr", "(JFFF)V", (void*)Gainmap_setEpsilonSdr},
+        {"nGetEpsilonSdr", "(J[F)V", (void*)Gainmap_getEpsilonSdr},
+        {"nSetEpsilonHdr", "(JFFF)V", (void*)Gainmap_setEpsilonHdr},
+        {"nGetEpsilonHdr", "(J[F)V", (void*)Gainmap_getEpsilonHdr},
+        {"nSetDisplayRatioHdr", "(JF)V", (void*)Gainmap_setDisplayRatioHdr},
+        {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr},
+        {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr},
+        {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr},
 };
 
 int register_android_graphics_Gainmap(JNIEnv* env) {
     gGainmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Gainmap"));
     gGainmap_constructorMethodID =
-            GetMethodIDOrDie(env, gGainmap_class, "<init>", "(Landroid/graphics/Bitmap;JIZ)V");
+            GetMethodIDOrDie(env, gGainmap_class, "<init>", "(Landroid/graphics/Bitmap;J)V");
     return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods,
                                          NELEM(gGainmapMethods));
 }