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.
AddCursorOutline(int width,int height,uint32_t * data)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.
AlphaMul(uint32_t * data,int width,int height)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.
HasAlphaChannel(const uint32_t * data,int stride,int width,int height)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
CreateMouseCursorFromHCursor(HDC dc,HCURSOR cursor)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