Implement scaling detection in WindowCapturerWin

WindowCapturerWin wrongly calculate the image size if the application it target
does not support high DPI. It causes part of the output frame black. See bug for
details.

Bug: webrtc:8112
Change-Id: I33c66dfa977ec08a29c56ff86ae37320b1459c87
Reviewed-on: https://chromium-review.googlesource.com/634383
Commit-Queue: Zijie He <zijiehe@chromium.org>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#19531}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: af5686a229eb16a68771d7bad204e7aea8d89958
diff --git a/modules/desktop_capture/desktop_geometry.cc b/modules/desktop_capture/desktop_geometry.cc
index 33d6070..5fadea2 100644
--- a/modules/desktop_capture/desktop_geometry.cc
+++ b/modules/desktop_capture/desktop_geometry.cc
@@ -70,5 +70,10 @@
   bottom_ += bottom_offset;
 }
 
+void DesktopRect::Scale(double horizontal, double vertical) {
+  right_ += width() * (horizontal - 1);
+  bottom_ += height() * (vertical - 1);
+}
+
 }  // namespace webrtc
 
diff --git a/modules/desktop_capture/desktop_geometry.h b/modules/desktop_capture/desktop_geometry.h
index e14ab70..71d4173 100644
--- a/modules/desktop_capture/desktop_geometry.h
+++ b/modules/desktop_capture/desktop_geometry.h
@@ -142,6 +142,10 @@
               int32_t right_offset,
               int32_t bottom_offset);
 
+  // Scales current DesktopRect. This function does not impact the |top_| and
+  // |left_|.
+  void Scale(double horizontal, double vertical);
+
  private:
   DesktopRect(int32_t left, int32_t top, int32_t right, int32_t bottom)
       : left_(left), top_(top), right_(right), bottom_(bottom) {
diff --git a/modules/desktop_capture/desktop_geometry_unittest.cc b/modules/desktop_capture/desktop_geometry_unittest.cc
index 0628d9a..520b1f9 100644
--- a/modules/desktop_capture/desktop_geometry_unittest.cc
+++ b/modules/desktop_capture/desktop_geometry_unittest.cc
@@ -66,4 +66,41 @@
   ASSERT_TRUE(rect.is_empty());
 }
 
+TEST(DesktopRectTest, Scale) {
+  DesktopRect rect = DesktopRect::MakeXYWH(100, 100, 100, 100);
+  rect.Scale(1.1, 1.1);
+  ASSERT_EQ(rect.top(), 100);
+  ASSERT_EQ(rect.left(), 100);
+  ASSERT_EQ(rect.width(), 110);
+  ASSERT_EQ(rect.height(), 110);
+
+  rect = DesktopRect::MakeXYWH(100, 100, 100, 100);
+  rect.Scale(0.01, 0.01);
+  ASSERT_EQ(rect.top(), 100);
+  ASSERT_EQ(rect.left(), 100);
+  ASSERT_EQ(rect.width(), 1);
+  ASSERT_EQ(rect.height(), 1);
+
+  rect = DesktopRect::MakeXYWH(100, 100, 100, 100);
+  rect.Scale(1.1, 0.9);
+  ASSERT_EQ(rect.top(), 100);
+  ASSERT_EQ(rect.left(), 100);
+  ASSERT_EQ(rect.width(), 110);
+  ASSERT_EQ(rect.height(), 90);
+
+  rect = DesktopRect::MakeXYWH(0, 0, 100, 100);
+  rect.Scale(1.1, 1.1);
+  ASSERT_EQ(rect.top(), 0);
+  ASSERT_EQ(rect.left(), 0);
+  ASSERT_EQ(rect.width(), 110);
+  ASSERT_EQ(rect.height(), 110);
+
+  rect = DesktopRect::MakeXYWH(0, 100, 100, 100);
+  rect.Scale(1.1, 1.1);
+  ASSERT_EQ(rect.top(), 100);
+  ASSERT_EQ(rect.left(), 0);
+  ASSERT_EQ(rect.width(), 110);
+  ASSERT_EQ(rect.height(), 110);
+}
+
 }  // namespace webrtc
diff --git a/modules/desktop_capture/win/window_capture_utils.cc b/modules/desktop_capture/win/window_capture_utils.cc
index 07da6ee..4241b81 100644
--- a/modules/desktop_capture/win/window_capture_utils.cc
+++ b/modules/desktop_capture/win/window_capture_utils.cc
@@ -113,6 +113,18 @@
   return region_type;
 }
 
+bool GetDcSize(HDC hdc, DesktopSize* size) {
+  win::ScopedGDIObject<HGDIOBJ, win::DeleteObjectTraits<HGDIOBJ>>
+      scoped_hgdi(GetCurrentObject(hdc, OBJ_BITMAP));
+  BITMAP bitmap;
+  memset(&bitmap, 0, sizeof(BITMAP));
+  if (GetObject(scoped_hgdi.Get(), sizeof(BITMAP), &bitmap) == 0) {
+    return false;
+  }
+  size->set(bitmap.bmWidth, bitmap.bmHeight);
+  return true;
+}
+
 AeroChecker::AeroChecker() : dwmapi_library_(nullptr), func_(nullptr) {
   // Try to load dwmapi.dll dynamically since it is not available on XP.
   dwmapi_library_ = LoadLibrary(L"dwmapi.dll");
diff --git a/modules/desktop_capture/win/window_capture_utils.h b/modules/desktop_capture/win/window_capture_utils.h
index c889c16..4c19d74 100644
--- a/modules/desktop_capture/win/window_capture_utils.h
+++ b/modules/desktop_capture/win/window_capture_utils.h
@@ -41,6 +41,10 @@
 // |window| if region type is SIMPLEREGION.
 int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result);
 
+// Retrieves the size of the |hdc|. This function returns false if native APIs
+// fail.
+bool GetDcSize(HDC hdc, DesktopSize* size);
+
 typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
 class AeroChecker {
  public:
diff --git a/modules/desktop_capture/window_capturer_win.cc b/modules/desktop_capture/window_capturer_win.cc
index 29fbebe..dde91aa 100644
--- a/modules/desktop_capture/window_capturer_win.cc
+++ b/modules/desktop_capture/window_capturer_win.cc
@@ -172,10 +172,23 @@
     return;
   }
 
+  DesktopRect original_rect;
+  DesktopRect cropped_rect;
+  // TODO(zijiehe): GetCroppedWindowRect() is not accurate, Windows won't draw
+  // the content below the |window_| with PrintWindow() or BitBlt(). See bug
+  // https://bugs.chromium.org/p/webrtc/issues/detail?id=8157.
+  if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) {
+    LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
+    callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
+    return;
+  }
+
   // Return a 1x1 black frame if the window is minimized or invisible, to match
   // behavior on mace. Window can be temporarily invisible during the
   // transition of full screen mode on/off.
-  if (IsIconic(window_) || !IsWindowVisible(window_)) {
+  if (original_rect.is_empty() ||
+      IsIconic(window_) ||
+      !IsWindowVisible(window_)) {
     std::unique_ptr<DesktopFrame> frame(
         new BasicDesktopFrame(DesktopSize(1, 1)));
     memset(frame->data(), 0, frame->stride() * frame->size().height());
@@ -186,14 +199,6 @@
     return;
   }
 
-  DesktopRect original_rect;
-  DesktopRect cropped_rect;
-  if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) {
-    LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
-    callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
-    return;
-  }
-
   HDC window_dc = GetWindowDC(window_);
   if (!window_dc) {
     LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
@@ -201,6 +206,29 @@
     return;
   }
 
+  DesktopSize window_dc_size;
+  if (GetDcSize(window_dc, &window_dc_size)) {
+    // The |window_dc_size| is used to detect the scaling of the original
+    // window. If the application does not support high-DPI settings, it will
+    // be scaled by Windows according to the scaling setting.
+    // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8
+    // So the size of the |window_dc|, i.e. the bitmap we can retrieve from
+    // PrintWindow() or BitBlt() function, will be smaller than
+    // |original_rect| and |cropped_rect|. Part of the captured desktop frame
+    // will be black. See
+    // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for
+    // details.
+
+    // If |window_dc_size| is smaller than |window_rect|, let's resize both
+    // |original_rect| and |cropped_rect| according to the scaling factor.
+    const double vertical_scale =
+        static_cast<double>(window_dc_size.width()) / original_rect.width();
+    const double horizontal_scale =
+        static_cast<double>(window_dc_size.height()) / original_rect.height();
+    original_rect.Scale(vertical_scale, horizontal_scale);
+    cropped_rect.Scale(vertical_scale, horizontal_scale);
+  }
+
   std::unique_ptr<DesktopFrameWin> frame(
       DesktopFrameWin::Create(cropped_rect.size(), nullptr, window_dc));
   if (!frame.get()) {