1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/views/tabs/native_view_photobooth_win.h" 6 7 #include "content/browser/tab_contents/tab_contents.h" 8 #include "third_party/skia/include/core/SkBitmap.h" 9 #include "ui/gfx/canvas_skia.h" 10 #include "ui/gfx/point.h" 11 #include "ui/gfx/rect.h" 12 #include "views/widget/widget.h" 13 14 namespace { 15 16 static BOOL CALLBACK MonitorEnumProc(HMONITOR monitor, HDC monitor_dc, 17 RECT* monitor_rect, LPARAM data) { 18 gfx::Point* point = reinterpret_cast<gfx::Point*>(data); 19 if (monitor_rect->right > point->x() && monitor_rect->bottom > point->y()) { 20 point->set_x(monitor_rect->right); 21 point->set_y(monitor_rect->bottom); 22 } 23 return TRUE; 24 } 25 26 gfx::Point GetCaptureWindowPosition() { 27 // Since the capture window must be visible to be painted, it must be opened 28 // off screen to avoid flashing. But if it is opened completely off-screen 29 // (e.g. at 0xFFFFx0xFFFF) then on Windows Vista it will not paint even if it 30 // _is_ visible. So we need to find the right/bottommost monitor, and 31 // position it so that 1x1 pixel is on-screen on that monitor which is enough 32 // to convince Vista to paint it. Don't ask why this is so - this appears to 33 // be a regression over XP. 34 gfx::Point point(0, 0); 35 EnumDisplayMonitors(NULL, NULL, &MonitorEnumProc, 36 reinterpret_cast<LPARAM>(&point)); 37 return gfx::Point(point.x() - 1, point.y() - 1); 38 } 39 40 } 41 42 /////////////////////////////////////////////////////////////////////////////// 43 // NativeViewPhotoboothWin, public: 44 45 // static 46 NativeViewPhotobooth* NativeViewPhotobooth::Create( 47 gfx::NativeView initial_view) { 48 return new NativeViewPhotoboothWin(initial_view); 49 } 50 51 NativeViewPhotoboothWin::NativeViewPhotoboothWin(HWND initial_hwnd) 52 : capture_window_(NULL), 53 current_hwnd_(initial_hwnd) { 54 DCHECK(IsWindow(current_hwnd_)); 55 CreateCaptureWindow(initial_hwnd); 56 } 57 58 NativeViewPhotoboothWin::~NativeViewPhotoboothWin() { 59 // Detach the attached HWND. The creator of the photo-booth is responsible 60 // for destroying it. 61 Replace(NULL); 62 capture_window_->Close(); 63 } 64 65 void NativeViewPhotoboothWin::Replace(HWND new_hwnd) { 66 if (IsWindow(current_hwnd_) && 67 GetParent(current_hwnd_) == capture_window_->GetNativeView()) { 68 // We need to hide the window too, so it doesn't show up in the TaskBar or 69 // be parented to the desktop. 70 ShowWindow(current_hwnd_, SW_HIDE); 71 SetParent(current_hwnd_, NULL); 72 } 73 current_hwnd_ = new_hwnd; 74 75 if (IsWindow(new_hwnd)) { 76 // Insert the TabContents into the capture window. 77 SetParent(current_hwnd_, capture_window_->GetNativeView()); 78 79 // Show the window (it may not be visible). This is the only safe way of 80 // doing this. ShowWindow does not work. 81 SetWindowPos(current_hwnd_, NULL, 0, 0, 0, 0, 82 SWP_DEFERERASE | SWP_NOACTIVATE | SWP_NOCOPYBITS | 83 SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOZORDER | 84 SWP_SHOWWINDOW | SWP_NOSIZE); 85 } 86 } 87 88 void NativeViewPhotoboothWin::PaintScreenshotIntoCanvas( 89 gfx::Canvas* canvas, 90 const gfx::Rect& target_bounds) { 91 // Our contained window may have been re-parented. Make sure it belongs to 92 // us until someone calls Replace(NULL). 93 if (IsWindow(current_hwnd_) && 94 GetParent(current_hwnd_) != capture_window_->GetNativeView()) { 95 Replace(current_hwnd_); 96 } 97 98 // We compel the contained HWND to paint now, synchronously. We do this to 99 // populate the device context with valid and current data. 100 RedrawWindow(current_hwnd_, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); 101 102 // Transfer the contents of the layered capture window to the screen-shot 103 // canvas' DIB. 104 HDC target_dc = canvas->BeginPlatformPaint(); 105 HDC source_dc = GetDC(current_hwnd_); 106 RECT window_rect = {0}; 107 GetWindowRect(current_hwnd_, &window_rect); 108 BitBlt(target_dc, target_bounds.x(), target_bounds.y(), 109 target_bounds.width(), target_bounds.height(), source_dc, 0, 0, 110 SRCCOPY); 111 // Windows screws up the alpha channel on all text it draws, and so we need 112 // to call makeOpaque _after_ the blit to correct for this. 113 canvas->AsCanvasSkia()->getTopPlatformDevice().makeOpaque( 114 target_bounds.x(), target_bounds.y(), target_bounds.width(), 115 target_bounds.height()); 116 ReleaseDC(current_hwnd_, source_dc); 117 canvas->EndPlatformPaint(); 118 } 119 120 /////////////////////////////////////////////////////////////////////////////// 121 // NativeViewPhotoboothWin, private: 122 123 void NativeViewPhotoboothWin::CreateCaptureWindow(HWND initial_hwnd) { 124 // Snapshotting a HWND is tricky - if the HWND is clipped (e.g. positioned 125 // partially off-screen) then just blitting from the HWND' DC to the capture 126 // bitmap would be incorrect, since the capture bitmap would show only the 127 // visible area of the HWND. 128 // 129 // The approach turns out to be to create a second layered window in 130 // hyperspace the to act as a "photo booth." The window is created with the 131 // size of the unclipped HWND, and we attach the HWND as a child, refresh the 132 // HWND' by calling |Paint| on it, and then blitting from the HWND's DC to 133 // the capture bitmap. This results in the entire unclipped HWND display 134 // bitmap being captured. 135 // 136 // The capture window must be layered so that Windows generates a backing 137 // store for it, so that blitting from a child window's DC produces data. If 138 // the window is not layered, because it is off-screen Windows does not 139 // retain its contents and blitting results in blank data. The capture window 140 // is a "basic" (1 level of alpha) layered window because that is the mode 141 // that supports having child windows (variable alpha layered windows do not 142 // support child HWNDs). 143 // 144 // This function sets up the off-screen capture window, and attaches the 145 // associated HWND to it. Note that the details are important here, see below 146 // for further comments. 147 // 148 RECT contents_rect; 149 GetClientRect(initial_hwnd, &contents_rect); 150 gfx::Point window_position = GetCaptureWindowPosition(); 151 gfx::Rect capture_bounds(window_position.x(), window_position.y(), 152 contents_rect.right - contents_rect.left, 153 contents_rect.bottom - contents_rect.top); 154 views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_POPUP); 155 params.transparent = true; 156 capture_window_ = views::Widget::CreateWidget(params); 157 // If the capture window isn't visible, blitting from the TabContents' 158 // HWND's DC to the capture bitmap produces blankness. 159 capture_window_->Init(NULL, capture_bounds); 160 capture_window_->Show(); 161 SetLayeredWindowAttributes( 162 capture_window_->GetNativeView(), RGB(0xFF, 0xFF, 0xFF), 0xFF, LWA_ALPHA); 163 164 Replace(initial_hwnd); 165 } 166