[Embedded Emulator] Foldable Support

When the screen is folded, getScreenshot() method will return an resized
image based on the requested width and height while keeping the aspect
ratio of the folded screen instead of the unfolded screen.

The idea is the same as snipping a rectangle within the unfolded screen
but we need to also consider rotation and reszing. There are 4 steps:

1, Calculate the scaling factor. Get new width, height for the entire
screen and compute the rectangle to snip.
2, Apply rotation to image and transform xOffset and yOffset in the
rectangle if necessary.
3, Resize based on new width and height.
4, Clip a rectangle from the resized screen.

Test: Manual. "adb emu fold" and "adb emu rotate" must work properly.
Bug: 171515275

Signed-off-by: Weilun Du <[email protected]>
Change-Id: I08bd318976e5060678eb184b93268f0297baaa02
diff --git a/include/render-utils/Renderer.h b/include/render-utils/Renderer.h
index bd369b0..4f4638a 100644
--- a/include/render-utils/Renderer.h
+++ b/include/render-utils/Renderer.h
@@ -280,17 +280,22 @@
     //
     // Note: Do not call this function again if it fails and *cPixels == 0
     //  swiftshader_indirect does not work with 3 channels
-    virtual int getScreenshot(
-            unsigned int nChannels,
-            unsigned int* width,
-            unsigned int* height,
-            uint8_t* pixels,
-            size_t* cPixels,
-            int displayId = 0,
-            int desiredWidth = 0,
-            int desiredHeight = 0,
-            int desiredRotation = 0) = 0;
-
+    //
+    // This function supports rectangle snipping by
+    // providing an |rect| parameter. The default value of {{0,0}, {0,0}}
+    // indicates the users wants to snip the entire screen.
+    // - |rect|  represents a rectangle within the screen defined by
+    // desiredWidth and desiredHeight.
+    virtual int getScreenshot(unsigned int nChannels,
+                              unsigned int* width,
+                              unsigned int* height,
+                              uint8_t* pixels,
+                              size_t* cPixels,
+                              int displayId = 0,
+                              int desiredWidth = 0,
+                              int desiredHeight = 0,
+                              SkinRotation desiredRotation = SKIN_ROTATION_0,
+                              SkinRect rect = {{0, 0}, {0, 0}}) = 0;
     virtual void snapshotOperationCallback(
             int snapshotterOp,
             int snapshotterStage) = 0;
diff --git a/stream-servers/FrameBuffer.cpp b/stream-servers/FrameBuffer.cpp
index 5e6ef2d..bbe98bd 100644
--- a/stream-servers/FrameBuffer.cpp
+++ b/stream-servers/FrameBuffer.cpp
@@ -722,13 +722,10 @@
             break;
         case PostCmd::Screenshot:
             m_postWorker->screenshot(
-                post.screenshot.cb,
-                post.screenshot.screenwidth,
-                post.screenshot.screenheight,
-                post.screenshot.format,
-                post.screenshot.type,
-                post.screenshot.rotation,
-                post.screenshot.pixels);
+                    post.screenshot.cb, post.screenshot.screenwidth,
+                    post.screenshot.screenheight, post.screenshot.format,
+                    post.screenshot.type, post.screenshot.rotation,
+                    post.screenshot.pixels, post.screenshot.rect);
             break;
         case PostCmd::Block:
             m_postWorker->block(std::move(post.block->scheduledSignal),
@@ -774,13 +771,11 @@
     res.wait();
     if (postOnlyOnMainThread && (PostCmd::Screenshot == post.cmd) &&
         emugl::get_emugl_window_operations().isRunningInUiThread()) {
-        post.cb->readPixelsScaled(
-            post.screenshot.screenwidth,
-            post.screenshot.screenheight,
-            post.screenshot.format,
-            post.screenshot.type,
-            post.screenshot.rotation,
-            post.screenshot.pixels);
+        post.cb->readPixelsScaled(post.screenshot.screenwidth,
+                                  post.screenshot.screenheight,
+                                  post.screenshot.format, post.screenshot.type,
+                                  post.screenshot.rotation,
+                                  post.screenshot.pixels, post.screenshot.rect);
     } else {
         std::future<void> completeFuture =
             m_postThread.enqueue(Post(std::move(post)));
@@ -2799,7 +2794,7 @@
 
 int FrameBuffer::getScreenshot(unsigned int nChannels, unsigned int* width,
         unsigned int* height, uint8_t* pixels, size_t* cPixels, int displayId,
-        int desiredWidth, int desiredHeight, int desiredRotation) {
+        int desiredWidth, int desiredHeight, int desiredRotation, SkinRect rect) {
     AutoLock mutex(m_lock);
     uint32_t w, h, cb;
     if (!emugl::get_emugl_multi_display_operations().getMultiDisplay(displayId,
@@ -2835,10 +2830,38 @@
         return -1;
     }
 
-    *width = (desiredWidth == 0) ? w : desiredWidth;
-    *height = (desiredHeight == 0) ? h : desiredHeight;
+    screenWidth = (desiredWidth == 0) ? w : desiredWidth;
+    screenHeight = (desiredHeight == 0) ? h : desiredHeight;
 
-    int needed = nChannels * (*width) * (*height);
+    bool useSnipping = (rect.size.w != 0 && rect.size.h != 0);
+    if (useSnipping) {
+        if (desiredWidth == 0 || desiredHeight == 0) {
+            LOG(ERROR)
+                    << "Must provide non-zero desiredWidth and desireRectanlge "
+                    << "when using rectangle snipping";
+            *width = 0;
+            *height = 0;
+            *cPixels = 0;
+            return -1;
+        }
+        if ((rect.pos.x < 0 || rect.pos.y < 0) ||
+            (desiredWidth < rect.pos.x + rect.size.w ||
+             desiredHeight < rect.pos.y + rect.size.h)) {
+            return -1;
+        }
+    }
+
+    int needed = useSnipping ? (nChannels * rect.size.w * rect.size.h)
+                             : (nChannels * (*width) * (*height));
+
+    if (useSnipping) {
+        *width = rect.size.w;
+        *height = rect.size.h;
+    } else {
+        *width = screenWidth;
+        *height = screenHeight;
+    }
+
     if (*cPixels < needed) {
         *cPixels = needed;
         return -2;
@@ -2846,6 +2869,33 @@
     *cPixels = needed;
     if (desiredRotation == SKIN_ROTATION_90 || desiredRotation == SKIN_ROTATION_270) {
         std::swap(*width, *height);
+        std::swap(screenWidth, screenHeight);
+        std::swap(rect.size.w, rect.size.h);
+    }
+    // Transform the x, y coordinates given the rotation.
+    // Assume (0, 0) represents the top left corner of the screen.
+    if (useSnipping) {
+        int x, y;
+        switch (desiredRotation) {
+            case SKIN_ROTATION_0:
+                x = rect.pos.x;
+                y = rect.pos.y;
+                break;
+            case SKIN_ROTATION_90:
+                x = rect.pos.y;
+                y = rect.pos.x;
+                break;
+            case SKIN_ROTATION_180:
+                x = screenWidth - rect.pos.x - rect.size.w;
+                y = rect.pos.y;
+                break;
+            case SKIN_ROTATION_270:
+                x = rect.pos.y;
+                y = screenHeight - rect.pos.x - rect.size.h;
+                break;
+        }
+        rect.pos.x = x;
+        rect.pos.y = y;
     }
 
     GLenum format = nChannels == 3 ? GL_RGB : GL_RGBA;
@@ -2858,6 +2908,7 @@
     scrCmd.screenshot.type = GL_UNSIGNED_BYTE;
     scrCmd.screenshot.rotation = desiredRotation;
     scrCmd.screenshot.pixels = pixels;
+    scrCmd.screenshot.rect = rect;
 
     std::future<void> completeFuture = sendPostWorkerCmd(std::move(scrCmd));
     completeFuture.wait();
diff --git a/stream-servers/FrameBuffer.h b/stream-servers/FrameBuffer.h
index 785fe1f..8140076 100644
--- a/stream-servers/FrameBuffer.h
+++ b/stream-servers/FrameBuffer.h
@@ -572,6 +572,13 @@
     //
     // Note: Do not call this function again if it fails and *cPixels == 0
     //  swiftshader_indirect does not work with 3 channels
+    //
+    // This function supports rectangle snipping by
+    // providing an |rect| parameter. The default value of {{0,0}, {0,0}}
+    // indicates the users wants to snip the entire screen instead of a
+    // partial screen.
+    // - |rect|  represents a rectangle within the screen defined by
+    // desiredWidth and desiredHeight.
     int getScreenshot(unsigned int nChannels,
                       unsigned int* width,
                       unsigned int* height,
@@ -580,7 +587,8 @@
                       int displayId,
                       int desiredWidth,
                       int desiredHeight,
-                      int desiredRotation);
+                      SkinRotation desiredRotation,
+                      SkinRect rect = {{0, 0}, {0, 0}});
 
     void onLastColorBufferRef(uint32_t handle);
     ColorBufferPtr findColorBuffer(HandleType p_colorbuffer);
diff --git a/stream-servers/PostCommands.h b/stream-servers/PostCommands.h
index b8c9535..4e82a65 100644
--- a/stream-servers/PostCommands.h
+++ b/stream-servers/PostCommands.h
@@ -48,6 +48,7 @@
             GLenum type;
             int rotation;
             void* pixels;
+            SkinRect rect;
         } screenshot;
     };
 };
diff --git a/stream-servers/PostWorker.cpp b/stream-servers/PostWorker.cpp
index 01c436d..4f982c8 100644
--- a/stream-servers/PostWorker.cpp
+++ b/stream-servers/PostWorker.cpp
@@ -303,13 +303,14 @@
     GLenum format,
     GLenum type,
     int rotation,
-    void* pixels) {
+    void* pixels,
+    SkinRect rect) {
     if (m_displayVk) {
         GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) <<
                             "Screenshot not supported with native Vulkan swapchain enabled.";
     }
     cb->readPixelsScaled(
-        width, height, format, type, rotation, pixels);
+        width, height, format, type, rotation, pixels, rect);
 }
 
 void PostWorker::block(std::promise<void> scheduledSignal, std::future<void> continueSignal) {
diff --git a/stream-servers/PostWorker.h b/stream-servers/PostWorker.h
index af5ab76..8a01392 100644
--- a/stream-servers/PostWorker.h
+++ b/stream-servers/PostWorker.h
@@ -64,7 +64,7 @@
     void clear();
 
     void screenshot(ColorBuffer* cb, int screenwidth, int screenheight,
-                    GLenum format, GLenum type, int skinRotation, void* pixels);
+                    GLenum format, GLenum type, int skinRotation, void* pixels, SkinRect rect);
 
     // The block task will set the scheduledSignal promise when the task is scheduled, and wait
     // until continueSignal is ready before completes.
diff --git a/stream-servers/RendererImpl.cpp b/stream-servers/RendererImpl.cpp
index cecdada..4ffb018 100644
--- a/stream-servers/RendererImpl.cpp
+++ b/stream-servers/RendererImpl.cpp
@@ -380,21 +380,21 @@
     if (fb) fb->fillGLESUsages(usages);
 }
 
-int RendererImpl::getScreenshot(
-        unsigned int nChannels,
-        unsigned int* width,
-        unsigned int* height,
-        uint8_t* pixels,
-        size_t* cPixels,
-        int displayId = 0,
-        int desiredWidth = 0,
-        int desiredHeight = 0,
-        int desiredRotation = 0) {
+int RendererImpl::getScreenshot(unsigned int nChannels,
+                                unsigned int* width,
+                                unsigned int* height,
+                                uint8_t* pixels,
+                                size_t* cPixels,
+                                int displayId = 0,
+                                int desiredWidth = 0,
+                                int desiredHeight = 0,
+                                int desiredRotation = SKIN_ROTATION_0,
+                                SkinRect rect = {{0, 0}, {0, 0}}) {
     auto fb = FrameBuffer::getFB();
     if (fb) {
         return fb->getScreenshot(nChannels, width, height, pixels, cPixels,
                                  displayId, desiredWidth, desiredHeight,
-                                 desiredRotation);
+                                 desiredRotation, rect);
     }
     *cPixels = 0;
     return -1;
diff --git a/stream-servers/RendererImpl.h b/stream-servers/RendererImpl.h
index 31389d9..e9afcc9 100644
--- a/stream-servers/RendererImpl.h
+++ b/stream-servers/RendererImpl.h
@@ -109,10 +109,16 @@
     bool load(android::base::Stream* stream,
               const android::snapshot::ITextureLoaderPtr& textureLoader) final;
     void fillGLESUsages(android_studio::EmulatorGLESUsages*) final;
-    int getScreenshot(unsigned int nChannels, unsigned int* width,
-            unsigned int* height, uint8_t *pixels, size_t *cPixels,
-            int displayId, int desiredWidth, int desiredHeight,
-            int desiredRotation) final;
+    int getScreenshot(unsigned int nChannels,
+                      unsigned int* width,
+                      unsigned int* height,
+                      uint8_t* pixels,
+                      size_t* cPixels,
+                      int displayId,
+                      int desiredWidth,
+                      int desiredHeight,
+                      int desiredRotation,
+                      SkinRect rect) final;
 
     void snapshotOperationCallback(
             int snapshotterOp,
diff --git a/stream-servers/gl/ColorBufferGl.cpp b/stream-servers/gl/ColorBufferGl.cpp
index 5ce8366..2aeb406 100644
--- a/stream-servers/gl/ColorBufferGl.cpp
+++ b/stream-servers/gl/ColorBufferGl.cpp
@@ -409,11 +409,24 @@
                                    GLenum p_format,
                                    GLenum p_type,
                                    int rotation,
-                                   void* pixels) {
+                                   void* pixels,
+                                   SkinRect rect) {
     RecursiveScopedContextBind context(m_helper);
     if (!context.isOk()) {
         return;
     }
+    bool useSnipping = rect.size.w != 0 && rect.size.h != 0;
+    // Boundary check
+    if (useSnipping &&
+        (rect.pos.x < 0 || rect.pos.y < 0 || rect.pos.x + rect.size.w > width ||
+         rect.pos.y + rect.size.h > height)) {
+        LOG(ERROR) << "readPixelsScaled failed. Out-of-bound rectangle: ("
+                   << rect.pos.x << ", " << rect.pos.y << ") [" << rect.size.w
+                   << " x " << rect.size.h << "] "
+                   << "with screen "
+                   << "[" << width << " x " << height << "] ";
+        return;
+    }
     p_format = sGetUnsizedColorBufferFormat(p_format);
     touch();
     waitSync();
@@ -423,7 +436,12 @@
         GLint prevAlignment = 0;
         s_gles2.glGetIntegerv(GL_PACK_ALIGNMENT, &prevAlignment);
         s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, 1);
-        s_gles2.glReadPixels(0, 0, width, height, p_format, p_type, pixels);
+        if (useSnipping) {
+            s_gles2.glReadPixels(rect.pos.x, rect.pos.y, rect.size.w,
+                                 rect.size.h, p_format, p_type, pixels);
+        } else {
+            s_gles2.glReadPixels(0, 0, width, height, p_format, p_type, pixels);
+        }
         s_gles2.glPixelStorei(GL_PACK_ALIGNMENT, prevAlignment);
         unbindFbo();
     }
diff --git a/stream-servers/gl/ColorBufferGl.h b/stream-servers/gl/ColorBufferGl.h
index 5ec9514..f3a4f5d 100644
--- a/stream-servers/gl/ColorBufferGl.h
+++ b/stream-servers/gl/ColorBufferGl.h
@@ -121,13 +121,16 @@
                     GLenum p_format,
                     GLenum p_type,
                     void* pixels);
-
+    // Read the ColorBuffer instance's pixel values by first scaling
+    // to the size of width x height, then clipping a |rect| from the
+    // screen defined by width x height.
     void readPixelsScaled(int width,
                           int height,
                           GLenum p_format,
                           GLenum p_type,
                           int skinRotation,
-                          void* pixels);
+                          void* pixels,
+                          SkinRect rect);
 
     // Read cached YUV pixel values into host memory.
     void readPixelsYUVCached(int x,