blob: 8e3d34b39a1cc6d5bc801186f423a0fda76ab082 [file] [log] [blame]
alexeypa@chromium.org4af08782013-06-10 22:29:17 +00001/*
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>
kwiberg2bb3afa2016-03-16 15:58:08 -070014#include <memory>
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000015
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000016#include "webrtc/modules/desktop_capture/desktop_frame.h"
17#include "webrtc/modules/desktop_capture/desktop_geometry.h"
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +000018#include "webrtc/modules/desktop_capture/mouse_cursor.h"
Edward Lemurc20978e2017-07-06 19:44:34 +020019#include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
20#include "webrtc/rtc_base/logging.h"
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000021#include "webrtc/typedefs.h"
22
23namespace webrtc {
24
25namespace {
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
45const int kBytesPerPixel = DesktopFrame::kBytesPerPixel;
46
47// Pixel colors used when generating cursor outlines.
48const uint32_t kPixelRgbaBlack = RGBA(0, 0, 0, 0xff);
49const uint32_t kPixelRgbaWhite = RGBA(0xff, 0xff, 0xff, 0xff);
50const uint32_t kPixelRgbaTransparent = RGBA(0, 0, 0, 0);
51
52const uint32_t kPixelRgbWhite = RGB(0xff, 0xff, 0xff);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000053
54// Expands the cursor shape to add a white outline for visibility against
55// dark backgrounds.
56void 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
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +000076// Premultiplies RGB components of the pixel data in the given image by
77// the corresponding alpha components.
78void AlphaMul(uint32_t* data, int width, int height) {
kwiberg@webrtc.org2ebfac52015-01-14 10:51:54 +000079 static_assert(sizeof(uint32_t) == kBytesPerPixel,
80 "size of uint32 should be the number of bytes per pixel");
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000081
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +000082 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 }
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000092}
93
94// Scans a 32bpp bitmap looking for any pixels with non-zero alpha component.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +000095// Returns true if non-zero alpha is found. |stride| is expressed in pixels.
96bool HasAlphaChannel(const uint32_t* data, int stride, int width, int height) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +000097 const RGBQUAD* plane = reinterpret_cast<const RGBQUAD*>(data);
98 for (int y = 0; y < height; ++y) {
99 for (int x = 0; x < width; ++x) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000100 if (plane->rgbReserved != 0)
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000101 return true;
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000102 plane += 1;
103 }
104 plane += stride - width;
105 }
106
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000107 return false;
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000108}
109
110} // namespace
111
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000112MouseCursor* CreateMouseCursorFromHCursor(HDC dc, HCURSOR cursor) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000113 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;
kwiberg2bb3afa2016-03-16 15:58:08 -0700138 std::unique_ptr<uint32_t[]> mask_data(new uint32_t[width * height]);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000139
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();
kwiberg2bb3afa2016-03-16 15:58:08 -0700165 std::unique_ptr<DesktopFrame> image(
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000166 new BasicDesktopFrame(DesktopSize(width, height)));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000167 bool has_alpha = false;
168
169 if (is_color) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000170 image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000171 // Get the pixels from the color bitmap.
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000172 if (!GetDIBits(dc,
173 scoped_color,
174 0,
175 height,
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000176 image->data(),
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000177 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
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000184 // GetDIBits() does not provide any indication whether the bitmap has alpha
185 // channel, so we use HasAlphaChannel() below to find it out.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000186 has_alpha = HasAlphaChannel(reinterpret_cast<uint32_t*>(image->data()),
187 width, width, height);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000188 } 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
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000194 image.reset(new BasicDesktopFrame(DesktopSize(width, height)));
195
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000196 // The XOR mask becomes the color bitmap.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000197 memcpy(
jiayl@webrtc.orgbac5f0f2014-07-15 20:32:03 +0000198 image->data(), mask_plane + (width * height), image->stride() * height);
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000199 }
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;
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000205 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000206 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) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000220 if (*dst != 0) {
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000221 add_outline = true;
222 *dst = kPixelRgbaBlack;
223 } else {
224 *dst = kPixelRgbaTransparent;
225 }
226 } else {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000227 *dst = kPixelRgbaBlack ^ *dst;
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000228 }
229
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000230 ++dst;
231 ++mask;
232 }
233 }
234 if (add_outline) {
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000235 AddCursorOutline(
236 width, height, reinterpret_cast<uint32_t*>(image->data()));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000237 }
238 }
239
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000240 // Pre-multiply the resulting pixels since MouseCursor uses premultiplied
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +0000241 // images.
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000242 AlphaMul(reinterpret_cast<uint32_t*>(image->data()), width, height);
alexeypa@chromium.orgbebf3992013-09-05 19:32:46 +0000243
sergeyu@chromium.org2df89c02013-10-17 19:47:18 +0000244 return new MouseCursor(
245 image.release(), DesktopVector(hotspot_x, hotspot_y));
alexeypa@chromium.org4af08782013-06-10 22:29:17 +0000246}
247
248} // namespace webrtc