Gainmap: Implement gainmap support in BitmapFactory

Test: atest BitmapFactoryTest
Bug: 267215643
Change-Id: Icbcb37e5b3a8926edc8f64d2cb9647b46ced39f7
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 0d5995a..571ab83 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -2,6 +2,20 @@
 #define LOG_TAG "BitmapFactory"
 
 #include "BitmapFactory.h"
+
+#include <Gainmap.h>
+#include <HardwareBitmapUploader.h>
+#include <androidfw/Asset.h>
+#include <androidfw/ResourceTypes.h>
+#include <cutils/compiler.h>
+#include <fcntl.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include <memory>
+
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "FrontBufferedStream.h"
 #include "GraphicsJNI.h"
@@ -13,6 +27,7 @@
 #include "SkCanvas.h"
 #include "SkColorSpace.h"
 #include "SkEncodedImageFormat.h"
+#include "SkGainmapInfo.h"
 #include "SkImageInfo.h"
 #include "SkPaint.h"
 #include "SkPixelRef.h"
@@ -24,17 +39,6 @@
 #include "SkString.h"
 #include "Utils.h"
 
-#include <HardwareBitmapUploader.h>
-#include <nativehelper/JNIPlatformHelp.h>
-#include <androidfw/Asset.h>
-#include <androidfw/ResourceTypes.h>
-#include <cutils/compiler.h>
-#include <fcntl.h>
-#include <memory>
-#include <stdio.h>
-#include <stdint.h>
-#include <sys/stat.h>
-
 jfieldID gOptions_justBoundsFieldID;
 jfieldID gOptions_sampleSizeFieldID;
 jfieldID gOptions_configFieldID;
@@ -182,6 +186,115 @@
            needsFineScale(fullSize.height(), decodedSize.height(), sampleSize);
 }
 
+static bool decodeGainmap(std::unique_ptr<SkStream> gainmapStream, const SkGainmapInfo& gainmapInfo,
+                          sp<uirenderer::Gainmap>* outGainmap, const int sampleSize, float scale) {
+    std::unique_ptr<SkAndroidCodec> codec;
+    codec = SkAndroidCodec::MakeFromStream(std::move(gainmapStream), nullptr);
+    if (!codec) {
+        ALOGE("Can not create a codec for Gainmap.");
+        return false;
+    }
+    SkColorType decodeColorType = codec->computeOutputColorType(kN32_SkColorType);
+    sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(decodeColorType, nullptr);
+
+    SkISize size = codec->getSampledDimensions(sampleSize);
+
+    int scaledWidth = size.width();
+    int scaledHeight = size.height();
+    bool willScale = false;
+
+    // Apply a fine scaling step if necessary.
+    if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize) || scale != 1.0f) {
+        willScale = true;
+        // The operation below may loose precision (integer division), but it is put this way to
+        // mimic main image scale calculation
+        scaledWidth = static_cast<int>((codec->getInfo().width() / sampleSize) * scale + 0.5f);
+        scaledHeight = static_cast<int>((codec->getInfo().height() / sampleSize) * scale + 0.5f);
+    }
+
+    SkAlphaType alphaType = codec->computeOutputAlphaType(false);
+
+    const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
+                                                     alphaType, decodeColorSpace);
+
+    const SkImageInfo& bitmapInfo = decodeInfo;
+    SkBitmap decodeBitmap;
+    sk_sp<Bitmap> nativeBitmap = nullptr;
+
+    if (!decodeBitmap.setInfo(bitmapInfo)) {
+        ALOGE("Failed to setInfo.");
+        return false;
+    }
+
+    if (willScale) {
+        if (!decodeBitmap.tryAllocPixels(nullptr)) {
+            ALOGE("OOM allocating gainmap pixels.");
+            return false;
+        }
+    } else {
+        nativeBitmap = android::Bitmap::allocateHeapBitmap(&decodeBitmap);
+        if (!nativeBitmap) {
+            ALOGE("OOM allocating gainmap pixels.");
+            return false;
+        }
+    }
+
+    // Use SkAndroidCodec to perform the decode.
+    SkAndroidCodec::AndroidOptions codecOptions;
+    codecOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
+    codecOptions.fSampleSize = sampleSize;
+    SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodeBitmap.getPixels(),
+                                                     decodeBitmap.rowBytes(), &codecOptions);
+    switch (result) {
+        case SkCodec::kSuccess:
+        case SkCodec::kIncompleteInput:
+            break;
+        default:
+            ALOGE("Error decoding gainmap.");
+            return false;
+    }
+
+    if (willScale) {
+        SkBitmap gainmapBitmap;
+        const float scaleX = scaledWidth / float(decodeBitmap.width());
+        const float scaleY = scaledHeight / float(decodeBitmap.height());
+
+        SkColorType scaledColorType = decodeBitmap.colorType();
+        gainmapBitmap.setInfo(
+                bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
+
+        nativeBitmap = android::Bitmap::allocateHeapBitmap(&gainmapBitmap);
+        if (!nativeBitmap) {
+            ALOGE("OOM allocating gainmap pixels.");
+            return false;
+        }
+
+        SkPaint paint;
+        // kSrc_Mode instructs us to overwrite the uninitialized pixels in
+        // outputBitmap.  Otherwise we would blend by default, which is not
+        // what we want.
+        paint.setBlendMode(SkBlendMode::kSrc);
+
+        SkCanvas canvas(gainmapBitmap, SkCanvas::ColorBehavior::kLegacy);
+        canvas.scale(scaleX, scaleY);
+        decodeBitmap.setImmutable();  // so .asImage() doesn't make a copy
+        canvas.drawImage(decodeBitmap.asImage(), 0.0f, 0.0f,
+                         SkSamplingOptions(SkFilterMode::kLinear), &paint);
+    }
+
+    auto gainmap = sp<uirenderer::Gainmap>::make();
+    if (!gainmap) {
+        ALOGE("OOM allocating Gainmap");
+        return false;
+    }
+
+    gainmap->info = gainmapInfo;
+    gainmap->bitmap = std::move(nativeBitmap);
+    *outGainmap = std::move(gainmap);
+
+    return true;
+}
+
 static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
                         jobject padding, jobject options, jlong inBitmapHandle,
                         jlong colorSpaceHandle) {
@@ -485,6 +598,19 @@
         return nullObjectReturn("Got null SkPixelRef");
     }
 
+    bool hasGainmap = false;
+    SkGainmapInfo gainmapInfo;
+    std::unique_ptr<SkStream> gainmapStream = nullptr;
+    sp<uirenderer::Gainmap> gainmap = nullptr;
+    if (result == SkCodec::kSuccess) {
+        hasGainmap = codec->getAndroidGainmap(&gainmapInfo, &gainmapStream);
+    }
+
+    if (hasGainmap) {
+        hasGainmap =
+                decodeGainmap(std::move(gainmapStream), gainmapInfo, &gainmap, sampleSize, scale);
+    }
+
     if (!isMutable && javaBitmap == NULL) {
         // promise we will never change our pixels (great for sharing and pictures)
         outputBitmap.setImmutable();
@@ -492,6 +618,9 @@
 
     bool isPremultiplied = !requireUnpremultiplied;
     if (javaBitmap != nullptr) {
+        if (hasGainmap) {
+            reuseBitmap->setGainmap(std::move(gainmap));
+        }
         bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied);
         outputBitmap.notifyPixelsChanged();
         // If a java bitmap was passed in for reuse, pass it back
@@ -507,13 +636,22 @@
         if (!hardwareBitmap.get()) {
             return nullObjectReturn("Failed to allocate a hardware bitmap");
         }
+        if (hasGainmap) {
+            hardwareBitmap->setGainmap(std::move(gainmap));
+        }
+
         return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
                 ninePatchChunk, ninePatchInsets, -1);
     }
 
+    Bitmap* heapBitmap = defaultAllocator.getStorageObjAndReset();
+    if (hasGainmap && heapBitmap != nullptr) {
+        heapBitmap->setGainmap(std::move(gainmap));
+    }
+
     // now create the java bitmap
-    return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
-            bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
+    return bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags, ninePatchChunk, ninePatchInsets,
+                                -1);
 }
 
 static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,