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-Commit-Position: refs/heads/master@{#19531}
This commit is contained in:
Zijie He 2017-08-25 11:36:52 -07:00 committed by Commit Bot
parent eb442054ac
commit af5686a229
6 changed files with 99 additions and 9 deletions

View File

@ -70,5 +70,10 @@ void DesktopRect::Extend(int32_t left_offset,
bottom_ += bottom_offset;
}
void DesktopRect::Scale(double horizontal, double vertical) {
right_ += width() * (horizontal - 1);
bottom_ += height() * (vertical - 1);
}
} // namespace webrtc

View File

@ -142,6 +142,10 @@ class DesktopRect {
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) {

View File

@ -66,4 +66,41 @@ TEST(DesktopRectTest, EmptyRectUnionWithEmptyOne) {
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

View File

@ -113,6 +113,18 @@ int GetWindowRegionTypeWithBoundary(HWND window, DesktopRect* result) {
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");

View File

@ -41,6 +41,10 @@ bool GetWindowContentRect(HWND window, DesktopRect* result);
// |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:

View File

@ -172,10 +172,23 @@ void WindowCapturerWin::CaptureFrame() {
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 @@ void WindowCapturerWin::CaptureFrame() {
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 @@ void WindowCapturerWin::CaptureFrame() {
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()) {