Home | History | Annotate | Download | only in win
      1 /*
      2  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include "webrtc/modules/desktop_capture/win/cursor.h"
     12 
     13 #include <algorithm>
     14 
     15 #include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
     16 #include "webrtc/modules/desktop_capture/desktop_frame.h"
     17 #include "webrtc/modules/desktop_capture/desktop_geometry.h"
     18 #include "webrtc/modules/desktop_capture/mouse_cursor.h"
     19 #include "webrtc/system_wrappers/interface/compile_assert.h"
     20 #include "webrtc/system_wrappers/interface/logging.h"
     21 #include "webrtc/system_wrappers/interface/scoped_ptr.h"
     22 #include "webrtc/typedefs.h"
     23 
     24 namespace webrtc {
     25 
     26 namespace {
     27 
     28 #if defined(WEBRTC_ARCH_LITTLE_ENDIAN)
     29 
     30 #define RGBA(r, g, b, a) \
     31     ((((a) << 24) & 0xff000000) | \
     32     (((b) << 16) & 0xff0000) | \
     33     (((g) << 8) & 0xff00) | \
     34     ((r) & 0xff))
     35 
     36 #else  // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
     37 
     38 #define RGBA(r, g, b, a) \
     39     ((((r) << 24) & 0xff000000) | \
     40     (((g) << 16) & 0xff0000) | \
     41     (((b) << 8) & 0xff00) | \
     42     ((a) & 0xff))
     43 
     44 #endif  // !defined(WEBRTC_ARCH_LITTLE_ENDIAN)
     45 
     46 const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
     47 
     48 // Pixel colors used when generating cursor outlines.
     49 const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
     50 const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
     51 const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
     52 
     53 const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
     54 const uint32_t kPixelRgbBlack = RGB(0, 0, 0);
     55 
     56 // Expands the cursor shape to add a white outline for visibility against
     57 // dark backgrounds.
     58 void AddCursorOutline(int width, int height, uint32_t* data) {
     59   for (int y = 0; y < height; y++) {
     60     for (int x = 0; x < width; x++) {
     61       // If this is a transparent pixel (bgr == 0 and alpha = 0), check the
     62       // neighbor pixels to see if this should be changed to an outline pixel.
     63       if (*data == kPixelRgbaTransparent) {
     64         // Change to white pixel if any neighbors (top, bottom, left, right)
     65         // are black.
     66         if ((y > 0 && data[-width] == kPixelRgbaBlack) ||
     67             (y < height - 1 && data[width] == kPixelRgbaBlack) ||
     68             (x > 0 && data[-1] == kPixelRgbaBlack) ||
     69             (x < width - 1 && data[1] == kPixelRgbaBlack)) {
     70           *data = kPixelRgbaWhite;
     71         }
     72       }
     73       data++;
     74     }
     75   }
     76 }
     77 
     78 // Premultiplies RGB components of the pixel data in the given image by
     79 // the corresponding alpha components.
     80 void AlphaMul(uint32_t* data, int width, int height) {
     81   COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel,
     82                  size_of_uint32_should_be_the_bytes_per_pixel);
     83 
     84   for (uint32_t* data_end = data + width * height; data != data_end; ++data) {
     85     RGBQUAD* from = reinterpret_cast<RGBQUAD*>(data);
     86     RGBQUAD* to = reinterpret_cast<RGBQUAD*>(data);
     87     to->rgbBlue =
     88         (static_cast<uint16_t>(from->rgbBlue) * from->rgbReserved) / 0xff;
     89     to->rgbGreen =
     90         (static_cast<uint16_t>(from->rgbGreen) * from->rgbReserved) / 0xff;
     91     to->rgbRed =
     92         (static_cast<uint16_t>(from->rgbRed) * from->rgbReserved) / 0xff;
     93   }
     94 }
     95 
     96 // Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
     97 // Returns true if non-zero alpha is found. |stride| is expressed in pixels.
     98 bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) {
     99   const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
    100   for (int y = 0; y < height; ++y) {
    101     for (int x = 0; x < width; ++x) {
    102       if (plane->rgbReserved != 0)
    103         return true;
    104       plane += 1;
    105     }
    106     plane += stride - width;
    107   }
    108 
    109   return false;
    110 }
    111 
    112 }  // namespace
    113 
    114 MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) {
    115   ICONINFO iinfo;
    116   if (!GetIconInfo(cursor, &iinfo)) {
    117     LOG_F(LS_ERROR) << "Unable to get cursor icon info. Error = "
    118                     << GetLastError();
    119     return NULL;
    120   }
    121 
    122   int hotspot_x = iinfo.xHotspot;
    123   int hotspot_y = iinfo.yHotspot;
    124 
    125   // Make sure the bitmaps will be freed.
    126   win::ScopedBitmap scoped_mask(iinfo.hbmMask);
    127   win::ScopedBitmap scoped_color(iinfo.hbmColor);
    128   bool is_color = iinfo.hbmColor != NULL;
    129 
    130   // Get |scoped_mask| dimensions.
    131   BITMAP bitmap_info;
    132   if (!GetObject(scoped_mask, sizeof(bitmap_info), &bitmap_info)) {
    133     LOG_F(LS_ERROR) << "Unable to get bitmap info. Error = "
    134                     << GetLastError();
    135     return NULL;
    136   }
    137 
    138   int width = bitmap_info.bmWidth;
    139   int height = bitmap_info.bmHeight;
    140   scoped_ptr<uint32_t[]> mask_data(new uint32_t[width * height]);
    141 
    142   // Get pixel data from |scoped_mask| converting it to 32bpp along the way.
    143   // GetDIBits() sets the alpha component of every pixel to 0.
    144   BITMAPV5HEADER bmi = {0};
    145   bmi.bV5Size = sizeof(bmi);
    146   bmi.bV5Width = width;
    147   bmi.bV5Height = -height;  // request a top-down bitmap.
    148   bmi.bV5Planes = 1;
    149   bmi.bV5BitCount = kBytesPerPixel * 8;
    150   bmi.bV5Compression = BI_RGB;
    151   bmi.bV5AlphaMask = 0xff000000;
    152   bmi.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
    153   bmi.bV5Intent = LCS_GM_BUSINESS;
    154   if (!GetDIBits(dc,
    155                  scoped_mask,
    156                  0,
    157                  height,
    158                  mask_data.get(),
    159                  reinterpret_cast<BITMAPINFO*>(&bmi),
    160                  DIB_RGB_COLORS)) {
    161     LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
    162                     << GetLastError();
    163     return NULL;
    164   }
    165 
    166   uint32_t* mask_plane = mask_data.get();
    167   scoped_ptr<DesktopFrame> image(
    168       new BasicDesktopFrame(DesktopSize(width, height)));
    169   bool has_alpha = false;
    170 
    171   if (is_color) {
    172     image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
    173     // Get the pixels from the color bitmap.
    174     if (!GetDIBits(dc,
    175                    scoped_color,
    176                    0,
    177                    height,
    178                    image->data(),
    179                    reinterpret_cast<BITMAPINFO*>(&bmi),
    180                    DIB_RGB_COLORS)) {
    181       LOG_F(LS_ERROR) << "Unable to get bitmap bits. Error = "
    182                       << GetLastError();
    183       return NULL;
    184     }
    185 
    186     // GetDIBits() does not provide any indication whether the bitmap has alpha
    187     // channel, so we use HasAlphaChannel() below to find it out.
    188     has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()),
    189                                 width, width, height);
    190   } else {
    191     // For non-color cursors, the mask contains both an AND and an XOR mask and
    192     // the height includes both. Thus, the width is correct, but we need to
    193     // divide by 2 to get the correct mask height.
    194     height /= 2;
    195 
    196     image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
    197 
    198     // The XOR mask becomes the color bitmap.
    199     memcpy(
    200         image->data(), mask_plane + (width * height), image->stride() * width);
    201   }
    202 
    203   // Reconstruct transparency from the mask if the color image does not has
    204   // alpha channel.
    205   if (!has_alpha) {
    206     bool add_outline = false;
    207     uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
    208     uint32_t* mask = mask_plane;
    209     for (int y = 0; y < height; y++) {
    210       for (int x = 0; x < width; x++) {
    211         // The two bitmaps combine as follows:
    212         //  mask  color   Windows Result   Our result    RGB   Alpha
    213         //   0     00      Black            Black         00    ff
    214         //   0     ff      White            White         ff    ff
    215         //   1     00      Screen           Transparent   00    00
    216         //   1     ff      Reverse-screen   Black         00    ff
    217         //
    218         // Since we don't support XOR cursors, we replace the "Reverse Screen"
    219         // with black. In this case, we also add an outline around the cursor
    220         // so that it is visible against a dark background.
    221         if (*mask == kPixelRgbWhite) {
    222           if (*dst != 0) {
    223             add_outline = true;
    224             *dst = kPixelRgbaBlack;
    225           } else {
    226             *dst = kPixelRgbaTransparent;
    227           }
    228         } else {
    229           *dst = kPixelRgbaBlack ^ *dst;
    230         }
    231 
    232         ++dst;
    233         ++mask;
    234       }
    235     }
    236     if (add_outline) {
    237       AddCursorOutline(
    238           width, height, reinterpret_cast<uint32_t*>(image->data()));
    239     }
    240   }
    241 
    242   // Pre-multiply the resulting pixels since MouseCursor uses premultiplied
    243   // images.
    244   AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height);
    245 
    246   return new MouseCursor(
    247       image.release(), DesktopVector(hotspot_x, hotspot_y));
    248 }
    249 
    250 }  // namespace webrtc
    251