Tonemap in RecordingCanvas

Intecepts bitmap calls to tonemap whenever the source is HDR (PQ/HLG)
and the destination is SDR.

Also, fix the following bugs discovered as part of testing:
1. Don't implicitly cast to booleans when extracting transfer functions
   from a dataspace in hwui's tonemapper.
2. Fix some typos in defining the HLG/PQ transfer functions.

Bug: 261088450
Test: New ColorBitmapActivity in HwAccelerationTest
Change-Id: I9d9d68fc4f57b999b3c6d4156bef281b4409f37e
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 2427dec..4c669b8 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -212,9 +212,9 @@
             new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
     private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
             new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
-                0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+                0.28466892f, 0.5599107f, -11 / 12.0f, -3.0f, true);
     private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
-            new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+            new Rgb.TransferParameters(-107 / 128.0f, 1.0f, 32 / 2523.0f,
                 2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
 
     // See static initialization block next to #get(Named)
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 59e4b7a..2245aae 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -536,6 +536,7 @@
         "RootRenderNode.cpp",
         "SkiaCanvas.cpp",
         "SkiaInterpolator.cpp",
+        "Tonemapper.cpp",
         "VectorDrawable.cpp",
     ],
 
@@ -594,7 +595,6 @@
                 "ProfileData.cpp",
                 "ProfileDataContainer.cpp",
                 "Readback.cpp",
-                "Tonemapper.cpp",
                 "TreeInfo.cpp",
                 "WebViewFunctorManager.cpp",
                 "protos/graphicsstats.proto",
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index c46a2d3..291f4cf 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -45,4 +45,4 @@
 SkColor transformColor(ColorTransform transform, SkColor color);
 SkColor transformColorInverse(ColorTransform transform, SkColor color);
 
-}  // namespace android::uirenderer;
\ No newline at end of file
+}  // namespace android::uirenderer
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3f21940..bbe79d9 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -43,6 +43,7 @@
 #include "SkRegion.h"
 #include "SkTextBlob.h"
 #include "SkVertices.h"
+#include "Tonemapper.h"
 #include "VectorDrawable.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/FunctorDrawable.h"
@@ -334,7 +335,9 @@
     SkPaint paint;
     BitmapPalette palette;
     void draw(SkCanvas* c, const SkMatrix&) const {
-        c->drawImage(image.get(), x, y, sampling, &paint);
+        SkPaint newPaint = paint;
+        tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+        c->drawImage(image.get(), x, y, sampling, &newPaint);
     }
 };
 struct DrawImageRect final : Op {
@@ -356,7 +359,9 @@
     SkCanvas::SrcRectConstraint constraint;
     BitmapPalette palette;
     void draw(SkCanvas* c, const SkMatrix&) const {
-        c->drawImageRect(image.get(), src, dst, sampling, &paint, constraint);
+        SkPaint newPaint = paint;
+        tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+        c->drawImageRect(image.get(), src, dst, sampling, &newPaint, constraint);
     }
 };
 struct DrawImageLattice final : Op {
@@ -389,8 +394,10 @@
         auto flags =
                 (0 == fs) ? nullptr : pod<SkCanvas::Lattice::RectType>(
                                               this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor));
-        c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst,
-                            filter, &paint);
+        SkPaint newPaint = paint;
+        tonemapPaint(image->imageInfo(), c->imageInfo(), -1, newPaint);
+        c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, filter,
+                            &newPaint);
     }
 };
 
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index a7e76b6..0d39f0e 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -18,7 +18,10 @@
 
 #include <SkRuntimeEffect.h>
 #include <log/log.h>
+// libshaders only exists on Android devices
+#ifdef __ANDROID__
 #include <shaders/shaders.h>
+#endif
 
 #include "utils/Color.h"
 
@@ -26,6 +29,8 @@
 
 namespace {
 
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
 class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
 public:
     explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
@@ -59,20 +64,21 @@
     return effectBuilder.makeColorFilter();
 }
 
-static bool extractTransfer(ui::Dataspace dataspace) {
-    return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+static ui::Dataspace extractTransfer(ui::Dataspace dataspace) {
+    return static_cast<ui::Dataspace>(dataspace & HAL_DATASPACE_TRANSFER_MASK);
 }
 
 static bool isHdrDataspace(ui::Dataspace dataspace) {
     const auto transfer = extractTransfer(dataspace);
 
-    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+    return transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG;
 }
 
 static ui::Dataspace getDataspace(const SkImageInfo& image) {
     return static_cast<ui::Dataspace>(
             ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
 }
+#endif
 
 }  // namespace
 
@@ -80,6 +86,8 @@
 // shader and tag it on the supplied paint.
 void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
                   SkPaint& paint) {
+// custom tonemapping only exists on Android devices
+#ifdef __ANDROID__
     const auto sourceDataspace = getDataspace(source);
     const auto destinationDataspace = getDataspace(destination);
 
@@ -102,6 +110,9 @@
             paint.setColorFilter(colorFilter);
         }
     }
+#else
+    return;
+#endif
 }
 
 }  // namespace android::uirenderer
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 616f21cb..5a3d28a 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -120,6 +120,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="ColorBitmapActivity"
+                  android:label="Bitmaps/BitmapColors"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="PathOffsetActivity"
              android:label="Path/Offset"
              android:exported="true">
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
new file mode 100644
index 0000000..017de60
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -0,0 +1,196 @@
+/*
+ * 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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorSpace;
+import android.graphics.HardwareBufferRenderer;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageWriter;
+import android.os.Bundle;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback,
+        AdapterView.OnItemSelectedListener {
+
+    private static final int WIDTH = 512;
+    private static final int HEIGHT = 512;
+
+    private ImageView mImageView;
+    private SurfaceView mSurfaceView;
+    private HardwareBuffer mGradientBuffer;
+    private ImageWriter mImageWriter;
+    private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+    private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"};
+    private String mCurrentColorName = "sRGB";
+
+    private FutureTask<HardwareBuffer> authorGradientBuffer(HardwareBuffer buffer) {
+        HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
+        RenderNode node = new RenderNode("content");
+        node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight());
+
+        Canvas canvas = node.beginRecording();
+        LinearGradient gradient = new LinearGradient(
+                0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000,
+                0xFFFFFFFF, Shader.TileMode.CLAMP);
+        Paint paint = new Paint();
+        paint.setShader(gradient);
+        canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint);
+        node.endRecording();
+
+        renderer.setContentRoot(node);
+
+        ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+        FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer);
+        renderer.obtainRenderRequest()
+                .setColorSpace(colorSpace)
+                .draw(Executors.newSingleThreadExecutor(), result -> {
+                    result.getFence().await(Duration.ofSeconds(3));
+                    resolvedBuffer.run();
+                });
+        return resolvedBuffer;
+    }
+
+    private FutureTask<HardwareBuffer> getGradientBuffer() {
+        HardwareBuffer buffer = HardwareBuffer.create(
+                WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1,
+                HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                        | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
+        return authorGradientBuffer(buffer);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        try {
+
+            mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+
+            ArrayAdapter<String> adapter = new ArrayAdapter<>(
+                    this, android.R.layout.simple_spinner_item, mColorNames);
+
+            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            Spinner spinner = new Spinner(this);
+            spinner.setAdapter(adapter);
+            spinner.setOnItemSelectedListener(this);
+
+            mGradientBuffer = getGradientBuffer().get();
+
+            LinearLayout linearLayout = new LinearLayout(this);
+            linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+            mImageView = new ImageView(this);
+
+            mSurfaceView = new SurfaceView(this);
+            mSurfaceView.getHolder().addCallback(this);
+
+            linearLayout.addView(spinner, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+
+            linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+            linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
+
+            setContentView(linearLayout);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private ColorSpace getFromName(String name) {
+        if (name.equals("sRGB")) {
+            return ColorSpace.get(ColorSpace.Named.SRGB);
+        } else if (name.equals("BT2020_HLG")) {
+            return ColorSpace.get(ColorSpace.Named.BT2020_HLG);
+        } else if (name.equals("BT2020_PQ")) {
+            return ColorSpace.get(ColorSpace.Named.BT2020_PQ);
+        }
+
+        throw new RuntimeException("Unrecognized Colorspace!");
+    }
+
+    private void populateBuffers() {
+        Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+                mGradientBuffer, ColorSpace.get(ColorSpace.Named.SRGB));
+        Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+        copy.setColorSpace(mColorSpace);
+        mImageView.setImageBitmap(copy);
+
+        try (Image image = mImageWriter.dequeueInputImage()) {
+            authorGradientBuffer(image.getHardwareBuffer()).get();
+            image.setDataSpace(mColorSpace.getDataSpace());
+            mImageWriter.queueInputImage(image);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        mImageWriter = new ImageWriter.Builder(holder.getSurface())
+                .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                        | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
+                        | HardwareBuffer.USAGE_COMPOSER_OVERLAY)
+                .build();
+        populateBuffers();
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        mImageWriter.close();
+        mImageWriter = null;
+    }
+
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        mCurrentColorName = mColorNames[position];
+        mColorSpace = getFromName(mCurrentColorName);
+        populateBuffers();
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+
+    }
+}