summaryrefslogtreecommitdiffstats
path: root/chrome/browser/views/tabs/native_view_photobooth_win.cc
blob: 1eb31256cc2e47a1e5797fdaf57812bab1ceaeb4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/views/tabs/native_view_photobooth_win.h"

#include "chrome/browser/tab_contents/tab_contents.h"
#include "gfx/canvas_skia.h"
#include "gfx/point.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "views/widget/widget_win.h"

namespace {

static BOOL CALLBACK MonitorEnumProc(HMONITOR monitor, HDC monitor_dc,
                                     RECT* monitor_rect, LPARAM data) {
  gfx::Point* point = reinterpret_cast<gfx::Point*>(data);
  if (monitor_rect->right > point->x() && monitor_rect->bottom > point->y()) {
    point->set_x(monitor_rect->right);
    point->set_y(monitor_rect->bottom);
  }
  return TRUE;
}

gfx::Point GetCaptureWindowPosition() {
  // Since the capture window must be visible to be painted, it must be opened
  // off screen to avoid flashing. But if it is opened completely off-screen
  // (e.g. at 0xFFFFx0xFFFF) then on Windows Vista it will not paint even if it
  // _is_ visible. So we need to find the right/bottommost monitor, and
  // position it so that 1x1 pixel is on-screen on that monitor which is enough
  // to convince Vista to paint it. Don't ask why this is so - this appears to
  // be a regression over XP.
  gfx::Point point(0, 0);
  EnumDisplayMonitors(NULL, NULL, &MonitorEnumProc,
                      reinterpret_cast<LPARAM>(&point));
  return gfx::Point(point.x() - 1, point.y() - 1);
}

}

///////////////////////////////////////////////////////////////////////////////
// NativeViewPhotoboothWin, public:

// static
NativeViewPhotobooth* NativeViewPhotobooth::Create(
    gfx::NativeView initial_view) {
  return new NativeViewPhotoboothWin(initial_view);
}

NativeViewPhotoboothWin::NativeViewPhotoboothWin(HWND initial_hwnd)
    : capture_window_(NULL),
      current_hwnd_(initial_hwnd) {
  DCHECK(IsWindow(current_hwnd_));
  CreateCaptureWindow(initial_hwnd);
}

NativeViewPhotoboothWin::~NativeViewPhotoboothWin() {
  // Detach the attached HWND. The creator of the photo-booth is responsible
  // for destroying it.
  Replace(NULL);
  capture_window_->Close();
}

void NativeViewPhotoboothWin::Replace(HWND new_hwnd) {
  if (IsWindow(current_hwnd_) &&
      GetParent(current_hwnd_) == capture_window_->GetNativeView()) {
    // We need to hide the window too, so it doesn't show up in the TaskBar or
    // be parented to the desktop.
    ShowWindow(current_hwnd_, SW_HIDE);
    SetParent(current_hwnd_, NULL);
  }
  current_hwnd_ = new_hwnd;

  if (IsWindow(new_hwnd)) {
    // Insert the TabContents into the capture window.
    SetParent(current_hwnd_, capture_window_->GetNativeView());

    // Show the window (it may not be visible). This is the only safe way of
    // doing this. ShowWindow does not work.
    SetWindowPos(current_hwnd_, NULL, 0, 0, 0, 0,
                 SWP_DEFERERASE | SWP_NOACTIVATE | SWP_NOCOPYBITS |
                     SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOZORDER |
                     SWP_SHOWWINDOW | SWP_NOSIZE);
  }
}

void NativeViewPhotoboothWin::PaintScreenshotIntoCanvas(
    gfx::Canvas* canvas,
    const gfx::Rect& target_bounds) {
  // Our contained window may have been re-parented. Make sure it belongs to
  // us until someone calls Replace(NULL).
  if (IsWindow(current_hwnd_) &&
      GetParent(current_hwnd_) != capture_window_->GetNativeView()) {
    Replace(current_hwnd_);
  }

  // We compel the contained HWND to paint now, synchronously. We do this to
  // populate the device context with valid and current data.
  RedrawWindow(current_hwnd_, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);

  // Transfer the contents of the layered capture window to the screen-shot
  // canvas' DIB.
  HDC target_dc = canvas->AsCanvasSkia()->beginPlatformPaint();
  HDC source_dc = GetDC(current_hwnd_);
  RECT window_rect = {0};
  GetWindowRect(current_hwnd_, &window_rect);
  BitBlt(target_dc, target_bounds.x(), target_bounds.y(),
         target_bounds.width(), target_bounds.height(), source_dc, 0, 0,
         SRCCOPY);
  // Windows screws up the alpha channel on all text it draws, and so we need
  // to call makeOpaque _after_ the blit to correct for this.
  canvas->AsCanvasSkia()->getTopPlatformDevice().makeOpaque(
      target_bounds.x(), target_bounds.y(), target_bounds.width(),
      target_bounds.height());
  ReleaseDC(current_hwnd_, source_dc);
  canvas->AsCanvasSkia()->endPlatformPaint();
}

///////////////////////////////////////////////////////////////////////////////
// NativeViewPhotoboothWin, private:

void NativeViewPhotoboothWin::CreateCaptureWindow(HWND initial_hwnd) {
  // Snapshotting a HWND is tricky - if the HWND is clipped (e.g. positioned
  // partially off-screen) then just blitting from the HWND' DC to the capture
  // bitmap would be incorrect, since the capture bitmap would show only the
  // visible area of the HWND.
  //
  // The approach turns out to be to create a second layered window in
  // hyperspace the to act as a "photo booth." The window is created with the
  // size of the unclipped HWND, and we attach the HWND as a child, refresh the
  // HWND' by calling |Paint| on it, and then blitting from the HWND's DC to
  // the capture bitmap. This results in the entire unclipped HWND display
  // bitmap being captured.
  //
  // The capture window must be layered so that Windows generates a backing
  // store for it, so that blitting from a child window's DC produces data. If
  // the window is not layered, because it is off-screen Windows does not
  // retain its contents and blitting results in blank data. The capture window
  // is a "basic" (1 level of alpha) layered window because that is the mode
  // that supports having child windows (variable alpha layered windows do not
  // support child HWNDs).
  //
  // This function sets up the off-screen capture window, and attaches the
  // associated HWND to it. Note that the details are important here, see below
  // for further comments.
  //
  CRect contents_rect;
  GetClientRect(initial_hwnd, &contents_rect);
  gfx::Point window_position = GetCaptureWindowPosition();
  gfx::Rect capture_bounds(window_position.x(), window_position.y(),
                           contents_rect.Width(), contents_rect.Height());
  capture_window_ = new views::WidgetWin;
  capture_window_->set_window_style(WS_POPUP);
  // WS_EX_TOOLWINDOW ensures the capture window doesn't produce a Taskbar
  // button.
  capture_window_->set_window_ex_style(WS_EX_LAYERED | WS_EX_TOOLWINDOW);
  capture_window_->Init(NULL, capture_bounds);
  // If the capture window isn't visible, blitting from the TabContents'
  // HWND's DC to the capture bitmap produces blankness.
  capture_window_->Show();
  SetLayeredWindowAttributes(
      capture_window_->GetNativeView(), RGB(0xFF, 0xFF, 0xFF), 0xFF, LWA_ALPHA);

  Replace(initial_hwnd);
}