Add Gainmap bitmap & imagedecoder

Bug: 266628247
Test: builds & boots
Change-Id: I0da44e0c48cf8a6b6f95e3b62f6d5f74bd6c1eab
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index e60506f..046373d 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -93,6 +93,7 @@
     private boolean mRecycled;
 
     private ColorSpace mColorSpace;
+    private Gainmap mGainmap;
 
     /*package*/ int mDensity = getDefaultDensity();
 
@@ -1897,6 +1898,27 @@
     }
 
     /**
+     * Returns whether or not this Bitmap contains a Gainmap.
+     * @hide
+     */
+    public boolean hasGainmap() {
+        checkRecycled("Bitmap is recycled");
+        return nativeHasGainmap(mNativePtr);
+    }
+
+    /**
+     * Returns the gainmap or null if the bitmap doesn't contain a gainmap
+     * @hide
+     */
+    public @Nullable Gainmap getGainmap() {
+        checkRecycled("Bitmap is recycled");
+        if (mGainmap == null) {
+            mGainmap = nativeExtractGainmap(mNativePtr);
+        }
+        return mGainmap;
+    }
+
+    /**
      * Fills the bitmap's pixels with the specified {@link Color}.
      *
      * @throws IllegalStateException if the bitmap is not mutable.
@@ -2380,6 +2402,8 @@
 
     private static native void nativeSetImmutable(long nativePtr);
 
+    private static native Gainmap nativeExtractGainmap(long nativePtr);
+
     // ---------------- @CriticalNative -------------------
 
     @CriticalNative
@@ -2387,4 +2411,7 @@
 
     @CriticalNative
     private static native boolean nativeIsBackedByAshmem(long nativePtr);
+
+    @CriticalNative
+    private static native boolean nativeHasGainmap(long nativePtr);
 }
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
new file mode 100644
index 0000000..a25a605
--- /dev/null
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.NonNull;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * 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
+ * channel Bitmap that represents the gainmap data itself.
+ *
+ * @hide
+ */
+public class Gainmap {
+    private final long mNativePtr;
+    private final Bitmap mGainmapImage;
+
+    // called from JNI and Bitmap_Delegate.
+    private Gainmap(Bitmap gainmapImage, long nativeGainmap, int allocationByteCount,
+            boolean fromMalloc) {
+        if (nativeGainmap == 0) {
+            throw new RuntimeException("internal error: native gainmap is 0");
+        }
+
+        mGainmapImage = gainmapImage;
+        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);
+    }
+
+    /**
+     * Returns the image data of the gainmap represented as a Bitmap
+     * @return
+     */
+    @NonNull
+    public Bitmap getGainmapImage() {
+        return mGainmapImage;
+    }
+
+    /**
+     * Sets the gainmap max metadata. 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);
+    }
+
+    /**
+     * Gets the gainmap max metadata. For single-plane gainmaps, all 3 components should be the
+     * same. The components are in r, g, b order.
+     */
+    @NonNull
+    public float[] getGainmapMax() {
+        float[] ret = new float[3];
+        nGetGainmapMax(mNativePtr, ret);
+        return ret;
+    }
+
+    /**
+     * Sets the maximum HDR ratio for the gainmap
+     */
+    @NonNull
+    public void setHdrRatioMax(float max) {
+        nSetHdrRatioMax(mNativePtr, max);
+    }
+
+    /**
+     * Gets the maximum HDR ratio for the gainmap
+     */
+    @NonNull
+    public float getHdrRatioMax() {
+        return nGetHdrRatioMax(mNativePtr);
+    }
+
+    /**
+     * Sets the maximum HDR ratio for the gainmap
+     */
+    @NonNull
+    public void setHdrRatioMin(float min) {
+        nSetHdrRatioMin(mNativePtr, min);
+    }
+
+    /**
+     * Gets the maximum HDR ratio for the gainmap
+     */
+    @NonNull
+    public float getHdrRatioMin() {
+        return nGetHdrRatioMin(mNativePtr);
+    }
+
+    private static native long nGetFinalizer();
+
+    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 nSetHdrRatioMax(long ptr, float max);
+    private static native float nGetHdrRatioMax(long ptr);
+
+    private static native void nSetHdrRatioMin(long ptr, float min);
+    private static native float nGetHdrRatioMin(long ptr);
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 8d4bda2..dd6c2bc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -347,6 +347,7 @@
         "jni/CreateJavaOutputStreamAdaptor.cpp",
         "jni/FontFamily.cpp",
         "jni/FontUtils.cpp",
+        "jni/Gainmap.cpp",
         "jni/Graphics.cpp",
         "jni/ImageDecoder.cpp",
         "jni/Interpolator.cpp",
diff --git a/libs/hwui/Gainmap.h b/libs/hwui/Gainmap.h
new file mode 100644
index 0000000..765f98a
--- /dev/null
+++ b/libs/hwui/Gainmap.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkGainmapInfo.h>
+#include <SkImage.h>
+#include <hwui/Bitmap.h>
+#include <utils/LightRefBase.h>
+
+namespace android::uirenderer {
+
+class Gainmap : public LightRefBase<Gainmap> {
+public:
+    SkGainmapInfo info;
+    sk_sp<Bitmap> bitmap;
+};
+
+}  // namespace android::uirenderer
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index f57d80c..c509ed4 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -55,6 +55,7 @@
 extern int register_android_graphics_ColorSpace(JNIEnv* env);
 extern int register_android_graphics_DrawFilter(JNIEnv* env);
 extern int register_android_graphics_FontFamily(JNIEnv* env);
+extern int register_android_graphics_Gainmap(JNIEnv* env);
 extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
 extern int register_android_graphics_Matrix(JNIEnv* env);
 extern int register_android_graphics_Paint(JNIEnv* env);
@@ -114,6 +115,7 @@
             REG_JNI(register_android_graphics_ColorFilter),
             REG_JNI(register_android_graphics_DrawFilter),
             REG_JNI(register_android_graphics_FontFamily),
+            REG_JNI(register_android_graphics_Gainmap),
             REG_JNI(register_android_graphics_HardwareRendererObserver),
             REG_JNI(register_android_graphics_ImageDecoder),
             REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index feafc23..0a755f0 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -34,6 +34,7 @@
 #include <binder/IServiceManager.h>
 #endif
 
+#include <Gainmap.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
 #include <SkEncodedImageFormat.h>
@@ -44,6 +45,7 @@
 #include <SkRect.h>
 #include <SkStream.h>
 #include <SkWebpEncoder.h>
+
 #include <limits>
 
 namespace android {
@@ -494,4 +496,14 @@
 
     return SkEncodeImage(stream, bitmap, fm, quality);
 }
+
+sp<uirenderer::Gainmap> Bitmap::gainmap() const {
+    LOG_ALWAYS_FATAL_IF(!hasGainmap(), "Bitmap doesn't have a gainmap");
+    return mGainmap;
+}
+
+void Bitmap::setGainmap(sp<uirenderer::Gainmap>&& gainmap) {
+    mGainmap = std::move(gainmap);
+}
+
 }  // namespace android
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 133f1fe..912d311 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -23,6 +23,10 @@
 #include <SkPixelRef.h>
 #include <SkRefCnt.h>
 #include <cutils/compiler.h>
+#include <utils/StrongPointer.h>
+
+#include <optional>
+
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
 #include <android/hardware_buffer.h>
 #endif
@@ -47,6 +51,7 @@
 };
 
 namespace uirenderer {
+class Gainmap;
 namespace renderthread {
 class RenderThread;
 }
@@ -119,6 +124,11 @@
     void getBounds(SkRect* bounds) const;
 
     bool isHardware() const { return mPixelStorageType == PixelStorageType::Hardware; }
+    bool hasGainmap() const { return mGainmap.get() != nullptr; }
+
+    sp<uirenderer::Gainmap> gainmap() const;
+
+    void setGainmap(sp<uirenderer::Gainmap>&& gainmap);
 
     PixelStorageType pixelStorageType() const { return mPixelStorageType; }
 
@@ -193,6 +203,8 @@
 
     bool mHasHardwareMipMap = false;
 
+    sp<uirenderer::Gainmap> mGainmap;
+
     union {
         struct {
             SkPixelRef* pixelRef;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index dd68f82..b1abe85 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -16,15 +16,20 @@
 
 #include "ImageDecoder.h"
 
-#include <hwui/Bitmap.h>
-#include <log/log.h>
-
+#include <Gainmap.h>
 #include <SkAndroidCodec.h>
 #include <SkBitmap.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkEncodedOrigin.h>
+#include <SkGainmapInfo.h>
 #include <SkPaint.h>
+#include <SkStream.h>
+#include <hwui/Bitmap.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <memory>
 
 #undef LOG_TAG
 #define LOG_TAG "ImageDecoder"
@@ -195,7 +200,7 @@
 }
 
 bool ImageDecoder::swapWidthHeight() const {
-    return SkEncodedOriginSwapsWidthHeight(mCodec->codec()->getOrigin());
+    return SkEncodedOriginSwapsWidthHeight(getOrigin());
 }
 
 int ImageDecoder::width() const {
@@ -316,7 +321,7 @@
         info.fFrameRect = SkIRect::MakeSize(dims);
     }
 
-    if (auto origin = mCodec->codec()->getOrigin(); origin != kDefault_SkEncodedOrigin) {
+    if (auto origin = getOrigin(); origin != kDefault_SkEncodedOrigin) {
         if (SkEncodedOriginSwapsWidthHeight(origin)) {
             dims = swapped(dims);
         }
@@ -400,7 +405,7 @@
     // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
     SkBitmap tmp;
     const bool scale = mDecodeSize != mTargetSize;
-    const auto origin = mCodec->codec()->getOrigin();
+    const auto origin = getOrigin();
     const bool handleOrigin = origin != kDefault_SkEncodedOrigin;
     SkMatrix outputMatrix;
     if (scale || handleOrigin || mCropRect) {
@@ -455,12 +460,15 @@
         mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
     }
 
+    ATRACE_BEGIN("getAndroidPixels");
     auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions);
+    ATRACE_END();
 
     // The next call to decode() may not provide zero initialized memory.
     mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
 
     if (scale || handleOrigin || mCropRect) {
+        ATRACE_NAME("Handling scale/origin/crop");
         SkBitmap scaledBm;
         if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) {
             return SkCodec::kInternalError;
@@ -478,3 +486,71 @@
     return result;
 }
 
+SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
+    ATRACE_CALL();
+    SkGainmapInfo gainmapInfo;
+    std::unique_ptr<SkStream> gainmapStream;
+    {
+        ATRACE_NAME("getAndroidGainmap");
+        if (!mCodec->getAndroidGainmap(&gainmapInfo, &gainmapStream)) {
+            return SkCodec::kSuccess;
+        }
+    }
+    auto gainmapCodec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream));
+    if (!gainmapCodec) {
+        ALOGW("Failed to create codec for gainmap stream");
+        return SkCodec::kInvalidInput;
+    }
+    ImageDecoder decoder{std::move(gainmapCodec)};
+    // Gainmap inherits the origin of the containing image
+    decoder.mOverrideOrigin.emplace(getOrigin());
+
+    const bool isScaled = width() != mTargetSize.width() || height() != mTargetSize.height();
+
+    if (isScaled) {
+        float scaleX = (float)mTargetSize.width() / width();
+        float scaleY = (float)mTargetSize.height() / height();
+        decoder.setTargetSize(decoder.width() * scaleX, decoder.height() * scaleY);
+    }
+
+    if (mCropRect) {
+        float sX = decoder.mTargetSize.width() / (float)mTargetSize.width();
+        float sY = decoder.mTargetSize.height() / (float)mTargetSize.height();
+        SkIRect crop = *mCropRect;
+        // TODO: Tweak rounding?
+        crop.fLeft *= sX;
+        crop.fRight *= sX;
+        crop.fTop *= sY;
+        crop.fBottom *= sY;
+        decoder.setCropRect(&crop);
+    }
+
+    SkImageInfo bitmapInfo = decoder.getOutputInfo();
+
+    SkBitmap bm;
+    if (!bm.setInfo(bitmapInfo)) {
+        ALOGE("Failed to setInfo properly");
+        return SkCodec::kInternalError;
+    }
+
+    // TODO: We don't currently parcel the gainmap, but if we should then also support
+    // the shared allocator
+    sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+    if (!nativeBitmap) {
+        ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(),
+              bitmapInfo.height());
+        return SkCodec::kInternalError;
+    }
+
+    SkCodec::Result result = decoder.decode(bm.getPixels(), bm.rowBytes());
+    bm.setImmutable();
+
+    if (result == SkCodec::kSuccess) {
+        auto gainmap = sp<uirenderer::Gainmap>::make();
+        gainmap->info = gainmapInfo;
+        gainmap->bitmap = std::move(nativeBitmap);
+        destination->setGainmap(std::move(gainmap));
+    }
+
+    return result;
+}
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index b6d73b3..97573e1 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -79,6 +79,8 @@
     // Set whether the ImageDecoder should handle RestorePrevious frames.
     void setHandleRestorePrevious(bool handle);
 
+    SkCodec::Result extractGainmap(Bitmap* destination);
+
 private:
     // State machine for keeping track of how to handle RestorePrevious (RP)
     // frames in decode().
@@ -115,6 +117,7 @@
     RestoreState mRestoreState;
     sk_sp<Bitmap> mRestoreFrame;
     std::optional<SkIRect> mCropRect;
+    std::optional<SkEncodedOrigin> mOverrideOrigin;
 
     ImageDecoder(const ImageDecoder&) = delete;
     ImageDecoder& operator=(const ImageDecoder&) = delete;
@@ -124,6 +127,10 @@
     bool swapWidthHeight() const;
     // Store/restore a frame if necessary. Returns false on error.
     bool handleRestorePrevious(const SkImageInfo&, void* pixels, size_t rowBytes);
+
+    SkEncodedOrigin getOrigin() const {
+        return mOverrideOrigin.has_value() ? *mOverrideOrigin : mCodec->codec()->getOrigin();
+    }
 };
 
 } // namespace android
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
old mode 100755
new mode 100644
index c68a6b9..10c287d
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -6,6 +6,7 @@
 #include <hwui/Paint.h>
 
 #include "CreateJavaOutputStreamAdaptor.h"
+#include "Gainmap.h"
 #include "GraphicsJNI.h"
 #include "HardwareBufferHelpers.h"
 #include "SkBitmap.h"
@@ -47,6 +48,8 @@
 
 namespace android {
 
+jobject Gainmap_extractFromBitmap(JNIEnv* env, const Bitmap& bitmap);
+
 class BitmapWrapper {
 public:
     explicit BitmapWrapper(Bitmap* bitmap)
@@ -1251,68 +1254,77 @@
     return bitmapHolder->bitmap().setImmutable();
 }
 
+static jboolean Bitmap_hasGainmap(CRITICAL_JNI_PARAMS_COMMA jlong bitmapHandle) {
+    LocalScopedBitmap bitmapHolder(bitmapHandle);
+    if (!bitmapHolder.valid()) return false;
+
+    return bitmapHolder->bitmap().hasGainmap();
+}
+
+static jobject Bitmap_extractGainmap(JNIEnv* env, jobject, jlong bitmapHandle) {
+    LocalScopedBitmap bitmapHolder(bitmapHandle);
+    if (!bitmapHolder.valid()) return nullptr;
+    if (!bitmapHolder->bitmap().hasGainmap()) return nullptr;
+
+    return Gainmap_extractFromBitmap(env, bitmapHolder->bitmap());
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gBitmapMethods[] = {
-    {   "nativeCreate",             "([IIIIIIZJ)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_creator },
-    {   "nativeCopy",               "(JIZ)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_copy },
-    {   "nativeCopyAshmem",         "(J)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_copyAshmem },
-    {   "nativeCopyAshmemConfig",   "(JI)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_copyAshmemConfig },
-    {   "nativeGetAshmemFD",        "(J)I", (void*)Bitmap_getAshmemFd },
-    {   "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
-    {   "nativeRecycle",            "(J)V", (void*)Bitmap_recycle },
-    {   "nativeReconfigure",        "(JIIIZ)V", (void*)Bitmap_reconfigure },
-    {   "nativeCompress",           "(JIILjava/io/OutputStream;[B)Z",
-        (void*)Bitmap_compress },
-    {   "nativeErase",              "(JI)V", (void*)Bitmap_erase },
-    {   "nativeErase",              "(JJJ)V", (void*)Bitmap_eraseLong },
-    {   "nativeRowBytes",           "(J)I", (void*)Bitmap_rowBytes },
-    {   "nativeConfig",             "(J)I", (void*)Bitmap_config },
-    {   "nativeHasAlpha",           "(J)Z", (void*)Bitmap_hasAlpha },
-    {   "nativeIsPremultiplied",    "(J)Z", (void*)Bitmap_isPremultiplied},
-    {   "nativeSetHasAlpha",        "(JZZ)V", (void*)Bitmap_setHasAlpha},
-    {   "nativeSetPremultiplied",   "(JZ)V", (void*)Bitmap_setPremultiplied},
-    {   "nativeHasMipMap",          "(J)Z", (void*)Bitmap_hasMipMap },
-    {   "nativeSetHasMipMap",       "(JZ)V", (void*)Bitmap_setHasMipMap },
-    {   "nativeCreateFromParcel",
-        "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_createFromParcel },
-    {   "nativeWriteToParcel",      "(JILandroid/os/Parcel;)Z",
-        (void*)Bitmap_writeToParcel },
-    {   "nativeExtractAlpha",       "(JJ[I)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_extractAlpha },
-    {   "nativeGenerationId",       "(J)I", (void*)Bitmap_getGenerationId },
-    {   "nativeGetPixel",           "(JII)I", (void*)Bitmap_getPixel },
-    {   "nativeGetColor",           "(JII)J", (void*)Bitmap_getColor },
-    {   "nativeGetPixels",          "(J[IIIIIII)V", (void*)Bitmap_getPixels },
-    {   "nativeSetPixel",           "(JIII)V", (void*)Bitmap_setPixel },
-    {   "nativeSetPixels",          "(J[IIIIIII)V", (void*)Bitmap_setPixels },
-    {   "nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V",
-                                            (void*)Bitmap_copyPixelsToBuffer },
-    {   "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V",
-                                            (void*)Bitmap_copyPixelsFromBuffer },
-    {   "nativeSameAs",             "(JJ)Z", (void*)Bitmap_sameAs },
-    {   "nativePrepareToDraw",      "(J)V", (void*)Bitmap_prepareToDraw },
-    {   "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount },
-    {   "nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;",
-        (void*)Bitmap_copyPreserveInternalConfig },
-    {   "nativeWrapHardwareBufferBitmap", "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
-        (void*) Bitmap_wrapHardwareBufferBitmap },
-    {   "nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;",
-        (void*) Bitmap_getHardwareBuffer },
-    {   "nativeComputeColorSpace",  "(J)Landroid/graphics/ColorSpace;", (void*)Bitmap_computeColorSpace },
-    {   "nativeSetColorSpace",      "(JJ)V", (void*)Bitmap_setColorSpace },
-    {   "nativeIsSRGB",             "(J)Z", (void*)Bitmap_isSRGB },
-    {   "nativeIsSRGBLinear",       "(J)Z", (void*)Bitmap_isSRGBLinear},
-    {   "nativeSetImmutable",       "(J)V", (void*)Bitmap_setImmutable},
+        {"nativeCreate", "([IIIIIIZJ)Landroid/graphics/Bitmap;", (void*)Bitmap_creator},
+        {"nativeCopy", "(JIZ)Landroid/graphics/Bitmap;", (void*)Bitmap_copy},
+        {"nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmem},
+        {"nativeCopyAshmemConfig", "(JI)Landroid/graphics/Bitmap;", (void*)Bitmap_copyAshmemConfig},
+        {"nativeGetAshmemFD", "(J)I", (void*)Bitmap_getAshmemFd},
+        {"nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer},
+        {"nativeRecycle", "(J)V", (void*)Bitmap_recycle},
+        {"nativeReconfigure", "(JIIIZ)V", (void*)Bitmap_reconfigure},
+        {"nativeCompress", "(JIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress},
+        {"nativeErase", "(JI)V", (void*)Bitmap_erase},
+        {"nativeErase", "(JJJ)V", (void*)Bitmap_eraseLong},
+        {"nativeRowBytes", "(J)I", (void*)Bitmap_rowBytes},
+        {"nativeConfig", "(J)I", (void*)Bitmap_config},
+        {"nativeHasAlpha", "(J)Z", (void*)Bitmap_hasAlpha},
+        {"nativeIsPremultiplied", "(J)Z", (void*)Bitmap_isPremultiplied},
+        {"nativeSetHasAlpha", "(JZZ)V", (void*)Bitmap_setHasAlpha},
+        {"nativeSetPremultiplied", "(JZ)V", (void*)Bitmap_setPremultiplied},
+        {"nativeHasMipMap", "(J)Z", (void*)Bitmap_hasMipMap},
+        {"nativeSetHasMipMap", "(JZ)V", (void*)Bitmap_setHasMipMap},
+        {"nativeCreateFromParcel", "(Landroid/os/Parcel;)Landroid/graphics/Bitmap;",
+         (void*)Bitmap_createFromParcel},
+        {"nativeWriteToParcel", "(JILandroid/os/Parcel;)Z", (void*)Bitmap_writeToParcel},
+        {"nativeExtractAlpha", "(JJ[I)Landroid/graphics/Bitmap;", (void*)Bitmap_extractAlpha},
+        {"nativeGenerationId", "(J)I", (void*)Bitmap_getGenerationId},
+        {"nativeGetPixel", "(JII)I", (void*)Bitmap_getPixel},
+        {"nativeGetColor", "(JII)J", (void*)Bitmap_getColor},
+        {"nativeGetPixels", "(J[IIIIIII)V", (void*)Bitmap_getPixels},
+        {"nativeSetPixel", "(JIII)V", (void*)Bitmap_setPixel},
+        {"nativeSetPixels", "(J[IIIIIII)V", (void*)Bitmap_setPixels},
+        {"nativeCopyPixelsToBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsToBuffer},
+        {"nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsFromBuffer},
+        {"nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs},
+        {"nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw},
+        {"nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount},
+        {"nativeCopyPreserveInternalConfig", "(J)Landroid/graphics/Bitmap;",
+         (void*)Bitmap_copyPreserveInternalConfig},
+        {"nativeWrapHardwareBufferBitmap",
+         "(Landroid/hardware/HardwareBuffer;J)Landroid/graphics/Bitmap;",
+         (void*)Bitmap_wrapHardwareBufferBitmap},
+        {"nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;",
+         (void*)Bitmap_getHardwareBuffer},
+        {"nativeComputeColorSpace", "(J)Landroid/graphics/ColorSpace;",
+         (void*)Bitmap_computeColorSpace},
+        {"nativeSetColorSpace", "(JJ)V", (void*)Bitmap_setColorSpace},
+        {"nativeIsSRGB", "(J)Z", (void*)Bitmap_isSRGB},
+        {"nativeIsSRGBLinear", "(J)Z", (void*)Bitmap_isSRGBLinear},
+        {"nativeSetImmutable", "(J)V", (void*)Bitmap_setImmutable},
+        {"nativeExtractGainmap", "(J)Landroid/graphics/Gainmap;", (void*)Bitmap_extractGainmap},
 
-    // ------------ @CriticalNative ----------------
-    {   "nativeIsImmutable",        "(J)Z", (void*)Bitmap_isImmutable},
-    {   "nativeIsBackedByAshmem",   "(J)Z", (void*)Bitmap_isBackedByAshmem}
+        // ------------ @CriticalNative ----------------
+        {"nativeIsImmutable", "(J)Z", (void*)Bitmap_isImmutable},
+        {"nativeIsBackedByAshmem", "(J)Z", (void*)Bitmap_isBackedByAshmem},
+        {"nativeHasGainmap", "(J)Z", (void*)Bitmap_hasGainmap},
 
 };
 
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
new file mode 100644
index 0000000..f2efbc7
--- /dev/null
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <Gainmap.h>
+
+#include "Bitmap.h"
+#include "GraphicsJNI.h"
+#include "graphics_jni_helpers.h"
+
+namespace android {
+
+static jclass gGainmap_class;
+static jmethodID gGainmap_constructorMethodID;
+
+using namespace uirenderer;
+
+static Gainmap* fromJava(jlong gainmap) {
+    return reinterpret_cast<Gainmap*>(gainmap);
+}
+
+static int getCreateFlags(const sk_sp<Bitmap>& bitmap) {
+    int flags = 0;
+    if (bitmap->info().alphaType() == kPremul_SkAlphaType) {
+        flags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
+    }
+    if (!bitmap->isImmutable()) {
+        flags |= android::bitmap::kBitmapCreateFlag_Mutable;
+    }
+    return flags;
+}
+
+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);
+
+    if (env->ExceptionCheck() != 0) {
+        // sadtrombone
+        gainmap->decStrong(0);
+        ALOGE("*** Uncaught exception returned from Java call!\n");
+        env->ExceptionDescribe();
+    }
+    return obj;
+}
+
+static void Gainmap_destructor(Gainmap* gainmap) {
+    gainmap->decStrong(0);
+}
+
+static jlong Gainmap_getNativeFinalizer(JNIEnv*, jobject) {
+    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};
+}
+
+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};
+    env->SetFloatArrayRegion(components, 0, 3, buf);
+}
+
+static void Gainmap_setHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr, jfloat max) {
+    fromJava(gainmapPtr)->info.fHdrRatioMax = max;
+}
+
+static jfloat Gainmap_getHdrRatioMax(JNIEnv*, jobject, jlong gainmapPtr) {
+    return fromJava(gainmapPtr)->info.fHdrRatioMax;
+}
+
+static void Gainmap_setHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr, jfloat min) {
+    fromJava(gainmapPtr)->info.fHdrRatioMin = min;
+}
+
+static jfloat Gainmap_getHdrRatioMin(JNIEnv*, jobject, jlong gainmapPtr) {
+    return fromJava(gainmapPtr)->info.fHdrRatioMin;
+}
+
+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},
+};
+
+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");
+    return android::RegisterMethodsOrDie(env, "android/graphics/Gainmap", gGainmapMethods,
+                                         NELEM(gGainmapMethods));
+}
+
+}  // namespace android
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index bad710d..add62b1 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -14,20 +14,10 @@
  * limitations under the License.
  */
 
-#include "Bitmap.h"
-#include "BitmapFactory.h"
-#include "ByteBufferStreamAdaptor.h"
-#include "CreateJavaOutputStreamAdaptor.h"
-#include "GraphicsJNI.h"
 #include "ImageDecoder.h"
-#include "NinePatchPeeker.h"
-#include "Utils.h"
-
-#include <hwui/Bitmap.h>
-#include <hwui/ImageDecoder.h>
-#include <HardwareBitmapUploader.h>
 
 #include <FrontBufferedStream.h>
+#include <HardwareBitmapUploader.h>
 #include <SkAndroidCodec.h>
 #include <SkBitmap.h>
 #include <SkColorSpace.h>
@@ -35,11 +25,22 @@
 #include <SkRect.h>
 #include <SkStream.h>
 #include <SkString.h>
-
 #include <androidfw/Asset.h>
 #include <fcntl.h>
+#include <gui/TraceUtils.h>
+#include <hwui/Bitmap.h>
+#include <hwui/ImageDecoder.h>
 #include <sys/stat.h>
 
+#include "Bitmap.h"
+#include "BitmapFactory.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "Gainmap.h"
+#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+
 using namespace android;
 
 static jclass    gImageDecoder_class;
@@ -246,6 +247,7 @@
                                           jboolean requireUnpremul, jboolean preferRamOverQuality,
                                           jboolean asAlphaMask, jlong colorSpaceHandle,
                                           jboolean extended) {
+    ATRACE_CALL();
     auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
     if (!decoder->setTargetSize(targetWidth, targetHeight)) {
         doThrowISE(env, "Could not scale to target size!");
@@ -336,10 +338,21 @@
         return nullptr;
     }
 
+    ATRACE_FORMAT("Decoding %dx%d bitmap", bitmapInfo.width(), bitmapInfo.height());
     SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes());
     jthrowable jexception = get_and_clear_exception(env);
-    int onPartialImageError = jexception ? kSourceException
-                                         : 0; // No error.
+    int onPartialImageError = jexception ? kSourceException : 0;  // No error.
+
+    // Only attempt to extract the gainmap if we're not post-processing, as we can't automatically
+    // mimic that to the gainmap and expect it to be meaningful. And also don't extract the gainmap
+    // if we're prioritizing RAM over quality, since the gainmap improves quality at the
+    // cost of RAM
+    if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) {
+        // The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead
+        result = decoder->extractGainmap(nativeBitmap.get());
+        jexception = get_and_clear_exception(env);
+    }
+
     switch (result) {
         case SkCodec::kSuccess:
             // Ignore the exception, since the decode was successful anyway.
@@ -450,6 +463,10 @@
             sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
             if (hwBitmap) {
                 hwBitmap->setImmutable();
+                if (nativeBitmap->hasGainmap()) {
+                    // TODO: Also convert to a HW gainmap image
+                    hwBitmap->setGainmap(nativeBitmap->gainmap());
+                }
                 return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
                                             ninePatchChunk, ninePatchInsets);
             }