Add gainmap support for screencap

There still would need to be support added to the PNG spec to properly
support gainmaps without affecting downstream clients. So, this patch:

1. Allows for screencap to (temporarily) export jpegs
2. Adds plumbing in HWUI's apex layer to encode gainmaps
3. Wires up the attachGainmap flag to allow screenshots to output a
   gainmap

Bug: 329470026
Flag: com.android.graphics.surfaceflinger.flags.true_hdr_screenshots
Test: adb screencap -j sdcard/test.jpeg
Change-Id: I210a3e24ad2cfd6e0c0a954f42b9171d9e82e991
diff --git a/cmds/screencap/Android.bp b/cmds/screencap/Android.bp
index c009c1f..16026ec 100644
--- a/cmds/screencap/Android.bp
+++ b/cmds/screencap/Android.bp
@@ -17,6 +17,7 @@
         "libutils",
         "libbinder",
         "libjnigraphics",
+        "libhwui",
         "libui",
         "libgui",
     ],
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 01b20f4..12de82a 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -15,36 +15,28 @@
  */
 
 #include <android/bitmap.h>
+#include <android/graphics/bitmap.h>
 #include <android/gui/DisplayCaptureArgs.h>
 #include <binder/ProcessState.h>
 #include <errno.h>
-#include <unistd.h>
-#include <stdio.h>
 #include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <getopt.h>
-
-#include <linux/fb.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <sys/wait.h>
-
-#include <android/bitmap.h>
-
-#include <binder/ProcessState.h>
-
 #include <ftl/concat.h>
 #include <ftl/optional.h>
+#include <getopt.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
-
+#include <linux/fb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <system/graphics.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 
-#include <system/graphics.h>
-
 using namespace android;
 
 #define COLORSPACE_UNKNOWN    0
@@ -85,11 +77,12 @@
 };
 }
 
-static const struct option LONG_OPTIONS[] = {
-        {"png", no_argument, nullptr, 'p'},
-        {"help", no_argument, nullptr, 'h'},
-        {"hint-for-seamless", no_argument, nullptr, LongOpts::HintForSeamless},
-        {0, 0, 0, 0}};
+static const struct option LONG_OPTIONS[] = {{"png", no_argument, nullptr, 'p'},
+                                             {"jpeg", no_argument, nullptr, 'j'},
+                                             {"help", no_argument, nullptr, 'h'},
+                                             {"hint-for-seamless", no_argument, nullptr,
+                                              LongOpts::HintForSeamless},
+                                             {0, 0, 0, 0}};
 
 static int32_t flinger2bitmapFormat(PixelFormat f)
 {
@@ -170,10 +163,11 @@
     return 0;
 }
 
-status_t saveImage(const char* fn, bool png, const ScreenCaptureResults& captureResults) {
+status_t saveImage(const char* fn, std::optional<AndroidBitmapCompressFormat> format,
+                   const ScreenCaptureResults& captureResults) {
     void* base = nullptr;
     ui::Dataspace dataspace = captureResults.capturedDataspace;
-    sp<GraphicBuffer> buffer = captureResults.buffer;
+    const sp<GraphicBuffer>& buffer = captureResults.buffer;
 
     status_t result = buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base);
 
@@ -188,22 +182,48 @@
         return 1;
     }
 
+    void* gainmapBase = nullptr;
+    sp<GraphicBuffer> gainmap = captureResults.optionalGainMap;
+
+    if (gainmap) {
+        result = gainmap->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &gainmapBase);
+        if (gainmapBase == nullptr || result != NO_ERROR) {
+            fprintf(stderr, "Failed to capture gainmap with error code (%d)\n", result);
+            gainmapBase = nullptr;
+            // Fall-through: just don't attempt to write the gainmap
+        }
+    }
+
     int fd = -1;
     if (fn == nullptr) {
         fd = dup(STDOUT_FILENO);
         if (fd == -1) {
             fprintf(stderr, "Error writing to stdout. (%s)\n", strerror(errno));
+            if (gainmapBase) {
+                gainmap->unlock();
+            }
+
+            if (base) {
+                buffer->unlock();
+            }
             return 1;
         }
     } else {
         fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
         if (fd == -1) {
             fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno));
+            if (gainmapBase) {
+                gainmap->unlock();
+            }
+
+            if (base) {
+                buffer->unlock();
+            }
             return 1;
         }
     }
 
-    if (png) {
+    if (format) {
         AndroidBitmapInfo info;
         info.format = flinger2bitmapFormat(buffer->getPixelFormat());
         info.flags = ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
@@ -211,16 +231,31 @@
         info.height = buffer->getHeight();
         info.stride = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat());
 
-        int result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base,
-                                            ANDROID_BITMAP_COMPRESS_FORMAT_PNG, 100, &fd,
+        int result;
+
+        if (gainmapBase) {
+            result = ABitmap_compressWithGainmap(&info, static_cast<ADataSpace>(dataspace), base,
+                                                 gainmapBase, captureResults.hdrSdrRatio, *format,
+                                                 100, &fd,
+                                                 [](void* fdPtr, const void* data,
+                                                    size_t size) -> bool {
+                                                     int bytesWritten =
+                                                             write(*static_cast<int*>(fdPtr), data,
+                                                                   size);
+                                                     return bytesWritten == size;
+                                                 });
+        } else {
+            result = AndroidBitmap_compress(&info, static_cast<int32_t>(dataspace), base, *format,
+                                            100, &fd,
                                             [](void* fdPtr, const void* data, size_t size) -> bool {
                                                 int bytesWritten = write(*static_cast<int*>(fdPtr),
                                                                          data, size);
                                                 return bytesWritten == size;
                                             });
+        }
 
         if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
-            fprintf(stderr, "Failed to compress PNG (error code: %d)\n", result);
+            fprintf(stderr, "Failed to compress (error code: %d)\n", result);
         }
 
         if (fn != NULL) {
@@ -245,6 +280,14 @@
     }
     close(fd);
 
+    if (gainmapBase) {
+        gainmap->unlock();
+    }
+
+    if (base) {
+        buffer->unlock();
+    }
+
     return 0;
 }
 
@@ -262,13 +305,17 @@
     gui::CaptureArgs captureArgs;
     const char* pname = argv[0];
     bool png = false;
+    bool jpeg = false;
     bool all = false;
     int c;
-    while ((c = getopt_long(argc, argv, "aphd:", LONG_OPTIONS, nullptr)) != -1) {
+    while ((c = getopt_long(argc, argv, "apjhd:", LONG_OPTIONS, nullptr)) != -1) {
         switch (c) {
             case 'p':
                 png = true;
                 break;
+            case 'j':
+                jpeg = true;
+                break;
             case 'd': {
                 errno = 0;
                 char* end = nullptr;
@@ -325,6 +372,14 @@
             baseName = filename.substr(0, filename.size()-4);
             suffix = ".png";
             png = true;
+        } else if (filename.ends_with(".jpeg")) {
+            baseName = filename.substr(0, filename.size() - 5);
+            suffix = ".jpeg";
+            jpeg = true;
+        } else if (filename.ends_with(".jpg")) {
+            baseName = filename.substr(0, filename.size() - 4);
+            suffix = ".jpg";
+            jpeg = true;
         } else {
             baseName = filename;
         }
@@ -350,6 +405,20 @@
         }
     }
 
+    if (png && jpeg) {
+        fprintf(stderr, "Ambiguous file type");
+        return 1;
+    }
+
+    std::optional<AndroidBitmapCompressFormat> format = std::nullopt;
+
+    if (png) {
+        format = ANDROID_BITMAP_COMPRESS_FORMAT_PNG;
+    } else if (jpeg) {
+        format = ANDROID_BITMAP_COMPRESS_FORMAT_JPEG;
+        captureArgs.attachGainmap = true;
+    }
+
     // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
     // not allowed to spawn any additional threads, but we still spawn
     // a binder thread from userspace when we call startThreadPool().
@@ -385,7 +454,7 @@
         if (!filename.empty()) {
             fn = filename.c_str();
         }
-        if (const status_t saveImageStatus = saveImage(fn, png, result) != 0) {
+        if (const status_t saveImageStatus = saveImage(fn, format, result) != 0) {
             fprintf(stderr, "Saving image failed.\n");
             return saveImageStatus;
         }