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
24namespace webrtc {
25
26namespace {
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
46const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
47
48// Pixel colors used when generating cursor outlines.
49const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
50const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
51const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
52
53const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
54const uint32_t kPixelRgbBlack = RGB(0, 0, 0);
55
56// Expands the cursor shape to add a white outline for visibility against
57// dark backgrounds.
58void 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.
80void 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.
98bool 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
114MouseCursor* 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() * height);
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