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