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