Home | History | Annotate | Download | only in tabs
      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