native_view_photobooth_win.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
14namespace {
15
16static 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
26gfx::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
46NativeViewPhotobooth* NativeViewPhotobooth::Create(
47    gfx::NativeView initial_view) {
48  return new NativeViewPhotoboothWin(initial_view);
49}
50
51NativeViewPhotoboothWin::NativeViewPhotoboothWin(HWND initial_hwnd)
52    : capture_window_(NULL),
53      current_hwnd_(initial_hwnd) {
54  DCHECK(IsWindow(current_hwnd_));
55  CreateCaptureWindow(initial_hwnd);
56}
57
58NativeViewPhotoboothWin::~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
65void 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
88void 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
123void 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