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