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